Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"c0bd07a621bc02d47fbe791dc34c12325d2e975d5eddfe3138745675a7ad6f4e","Cargo.toml":"4e5a4edf0eb435b2ab031faae106e7026fd6541dbc1d4c31e1e83f16c8aae8d1","Deploy.md":"9f9d13051fe81d630a89f28396867cf1b3dfd6521e61d776aa0e153a425e607c","LICENSE.md":"21c678fec510670dd3857ba36eb43af7fef58a85a10fd4d2a69a0875b713b2bb","README.md":"d43f2d6f93a72dde9add89a31f79a8be28801fd6a55acc270dd3641edabe82c9","deny.toml":"92baf06cad6395447818817eb290ae8eb7723520182e72ebe115cd448b9885ae","examples/distance2d.rs":"b582841ef0110c029c9748b66213c8268fbd88ab9c6a25d2b9039cea1ab6a0b7","examples/distance3d.rs":"8931049db46aef24ec466d1f6ad450b592dc99ecd3d923771a34c42da2b83f5a","examples/modify_timestep.rs":"2af978c4a2d630ec2ec4a159445067bda6a8a330d438b15eb3636163a2e3cf2b","examples/movetowards.rs":"9d09ad3c44d6287c104aa3dc56790907407312aa103fe3e433c04b86aa02df7e","src/automatic_systems.rs":"2a646a570579d05c47236da74f622bd1f082434a324810810a371d159bdb3857","src/kdtree.rs":"2ff7f196a4fa655e1804ee507b89b027147e3c989393b364792addf70e2f9ce0","src/lib.rs":"d9e3935853c0549f8246cf43ba6549fb197a60af3b0f25a7e48e31aa7a7e267c","src/plugin.rs":"e3f4e0249a81c7c1dc269efdc8440bf2ec75d2d5aae5ecbdc47fc2c092c1fe8b","src/point.rs":"f5b2588f32c04278c7f1bc08327bab2df9d6d4e0ca9036db4edc81264b3981ec","src/spatial_access.rs":"5e96e8be35991c1ce4fc26b48d87d76a03830f1cd01e15de505119efc4a55487","src/timestep.rs":"76b587998a1eff65e051cc360b69ce910b0224a36af9bc8829bcd50a9c0434f2"},"package":"97c520d6901718c2fdeddce2c60218a803714dfe1f346a5ab8cdbd7568094768"}

5067
vendor/bevy_spatial/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

99
vendor/bevy_spatial/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,99 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_spatial"
version = "0.11.0"
authors = ["laund <me@laund.moe>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A crate for tracking bevy entities in spatial indices."
documentation = "https://docs.rs/bevy_spatial"
readme = "README.md"
keywords = [
"gamedev",
"bevy",
"kdtree",
"spatial",
"neighbour",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/laundmo/bevy-spatial"
[features]
default = ["kdtree_rayon"]
kdtree = ["dep:kd-tree"]
kdtree_rayon = [
"kdtree",
"kd-tree/rayon",
]
[lib]
name = "bevy_spatial"
path = "src/lib.rs"
[[example]]
name = "distance2d"
path = "examples/distance2d.rs"
required-features = ["kdtree"]
[[example]]
name = "distance3d"
path = "examples/distance3d.rs"
required-features = ["kdtree"]
[[example]]
name = "modify_timestep"
path = "examples/modify_timestep.rs"
required-features = ["kdtree"]
[[example]]
name = "movetowards"
path = "examples/movetowards.rs"
required-features = ["kdtree"]
[dependencies.bevy]
version = "0.16"
default-features = false
[dependencies.kd-tree]
version = "0.6.0"
optional = true
[dependencies.num-traits]
version = "0.2.19"
[dependencies.typenum]
version = "1.18.0"
[dev-dependencies.bevy]
version = "0.16"
[dev-dependencies.rand]
version = "0.8"
[profile.dev]
opt-level = 1
[profile.dev.package.wgpu-types]
debug-assertions = false
[profile.dev.package."*"]
opt-level = 3
[profile.release-with-debug]
debug = 2
inherits = "release"

8
vendor/bevy_spatial/Deploy.md vendored Normal file
View File

@@ -0,0 +1,8 @@
1. make sure the version is bumped, in cargo.toml and in the readme
2. build docs locally and check
$ cargo doc --all-features --no-deps -p bevy_spatial
3. make sure wasm works
$ bevy run --example distance2d --release --no-default-features true --features kdtree web --open
4. publish the crate
$ cargo publish
5. create github release

6
vendor/bevy_spatial/LICENSE.md vendored Normal file
View File

@@ -0,0 +1,6 @@
bevy_spatial is dual-licensed under either
- MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
at your option.

61
vendor/bevy_spatial/README.md vendored Normal file
View File

@@ -0,0 +1,61 @@
# bevy_spatial
<p align="center">
<img src="https://i.laundmo.com/tENe0/rozUsOnA55.png/raw">
</p>
A bevy plugin to track your entities in spatial indices and query them.
![crates.io](https://img.shields.io/crates/v/bevy_spatial.svg)
Currently implemented features:
| Feature | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `kdtree` (default) | KD-Tree for spatial lookups which is fully recreated on update, but fast to recreate. Works well in most situations. |
```rust
use bevy_spatial::{AutomaticUpdate, KDTree3, TransformMode, SpatialAccess};
#[derive(Component, Default)]
struct TrackedByKDTree;
fn main() {
App::new()
.add_plugins(AutomaticUpdate::<TrackedByKDTree>::new()
.with_frequency(Duration::from_secs_f32(0.3))
.with_transform(TransformMode::GlobalTransform))
.add_systems(Update, use_neighbour);
// ...
}
type NNTree = KDTree3<TrackedByKDTree>; // type alias for later
// spawn some entities with the TrackedByKDTree component
fn use_neighbour(tree: Res<NNTree>){
if let Some((pos, entity)) = tree.nearest_neighbour(Vec3::ZERO) {
// pos: Vec3
// do something with the nearest entity here
}
}
```
For more details on usage see [Examples](https://github.com/laundmo/bevy-spatial/tree/main/examples)
## compatible bevy versions
| bevy | bevy_spatial |
|------|--------------|
| 0.16 | 0.11.0 |
| 0.15 | 0.10.0 |
| 0.14 | 0.9.1 |
| 0.13 | 0.8.0 |
| 0.12 | 0.7.0 |
| 0.11 | 0.6.0 |
| 0.10 | 0.5.0 |
| 0.9 | 0.4.0 |
| 0.8 | 0.3.0 |
| 0.8 | 0.2.1 |
| 0.7 | 0.1 |
wasm caveats: Since the rayon acceleration for kdtree is enabled by default, but rayon doesn't work on wasm, for projects targeting wasm you'll need to disable default features for bevy-spatial and re-enable `kdtree`

41
vendor/bevy_spatial/deny.toml vendored Normal file
View File

@@ -0,0 +1,41 @@
[advisories]
version = 2
ignore = [
# paste was announced as unmaintained with no explanation or replacement
# See: https://rustsec.org/advisories/RUSTSEC-2024-0436
# Bevy relies on this in multiple indirect ways, so ignoring it is the only feasible current solution
"RUSTSEC-2024-0436",
]
[licenses]
version = 2
allow = [
"0BSD",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"ISC",
"MIT",
"MIT-0",
"Unlicense",
"Zlib",
]
exceptions = [
{ name = "unicode-ident", allow = [
"Unicode-DFS-2016",
"Unicode-3.0",
] },
]
[bans]
multiple-versions = "allow"
wildcards = "deny"
deny = [{ name = "glam", deny-multiple-versions = true }]
[sources]
unknown-registry = "deny"
unknown-git = "deny"

View File

@@ -0,0 +1,166 @@
use std::ops::Deref;
use std::time::Duration;
use bevy::{
color::palettes::css as csscolors,
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
math::Vec3Swizzles,
prelude::*,
window::PrimaryWindow,
};
use bevy_spatial::{AutomaticUpdate, SpatialStructure};
use bevy_spatial::{SpatialAccess, kdtree::KDTree2};
// marker for entities tracked by the KDTree
#[derive(Component, Default)]
struct NearestNeighbourComponent;
// marker for the "cursor" entity
#[derive(Component)]
struct Cursor;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
present_mode: bevy::window::PresentMode::AutoNoVsync,
..default()
}),
..default()
}))
// Add the plugin, which takes the tracked component as a generic.
.add_plugins(
AutomaticUpdate::<NearestNeighbourComponent>::new()
.with_spatial_ds(SpatialStructure::KDTree2)
.with_frequency(Duration::from_millis(1)),
)
.add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin::default())
.insert_resource(Mouse2D { pos: Vec2::ZERO })
.add_systems(Startup, setup)
.add_systems(
Update,
(
update_mouse_pos,
(
mouse,
color,
reset_color.before(color),
collide_wall,
movement,
),
)
.chain(),
)
.run();
}
// type alias for easier usage later
type NNTree = KDTree2<NearestNeighbourComponent>;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn((
Cursor,
Sprite {
color: Color::srgb(0.0, 0.0, 1.0),
custom_size: Some(Vec2::new(10.0, 10.0)),
..default()
},
Transform {
translation: Vec3::ZERO,
..default()
},
));
let sprite = Sprite {
color: csscolors::ORANGE_RED.into(),
custom_size: Some(Vec2::new(6.0, 6.0)),
..default()
};
for x in -100..100 {
for y in -100..100 {
commands.spawn((
NearestNeighbourComponent,
sprite.clone(),
Transform {
translation: Vec3::new((x * 4) as f32, (y * 4) as f32, 0.0),
..default()
},
));
}
}
}
#[derive(Copy, Clone, Resource)]
struct Mouse2D {
pos: Vec2,
}
fn update_mouse_pos(
window: Single<&Window, With<PrimaryWindow>>,
camera: Single<(&Camera, &GlobalTransform)>,
mut mouse: ResMut<Mouse2D>,
) {
let (cam, cam_t) = camera.deref();
if let Some(w_pos) = window.cursor_position() {
if let Ok(pos) = cam.viewport_to_world_2d(cam_t, w_pos) {
mouse.pos = pos;
}
}
}
fn mouse(
mut commands: Commands,
mouse: Res<Mouse2D>,
treeaccess: Res<NNTree>,
mut transform: Single<&mut Transform, With<Cursor>>,
ms_buttons: Res<ButtonInput<MouseButton>>,
) {
let use_mouse = ms_buttons.pressed(MouseButton::Left);
if let Some((_pos, entity)) = treeaccess.nearest_neighbour(mouse.pos) {
transform.translation = mouse.pos.extend(0.0); // I don't really know what this is here for
if use_mouse {
commands.entity(entity.unwrap()).despawn();
}
}
}
fn color(
treeaccess: Res<NNTree>,
mouse: Res<Mouse2D>,
mut query: Query<&mut Sprite, With<NearestNeighbourComponent>>,
) {
for (_, entity) in treeaccess.within_distance(mouse.pos, 50.0) {
if let Ok(mut sprite) = query.get_mut(entity.unwrap()) {
sprite.color = Color::BLACK;
}
}
}
fn reset_color(mut query: Query<&mut Sprite, With<NearestNeighbourComponent>>) {
for mut sprite in &mut query {
sprite.color = csscolors::ORANGE_RED.into();
}
}
fn movement(mut query: Query<&mut Transform, With<NearestNeighbourComponent>>) {
for mut pos in &mut query {
let goal = pos.translation - Vec3::ZERO;
pos.translation += goal.normalize_or_zero();
}
}
fn collide_wall(
window: Single<&Window, With<PrimaryWindow>>,
mut query: Query<&mut Transform, With<NearestNeighbourComponent>>,
) {
let w = window.width() / 2.0;
let h = window.height() / 2.0;
for mut pos in &mut query {
let [x, y] = pos.translation.xy().to_array();
if y < -h || x < -w || y > h || x > w {
pos.translation = pos.translation.normalize_or_zero();
}
}
}

View File

@@ -0,0 +1,125 @@
use bevy::{
color::palettes::css as csscolors,
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
window::PrimaryWindow,
};
use bevy_spatial::{AutomaticUpdate, SpatialAccess, kdtree::KDTree3};
use std::ops::Deref;
#[derive(Component)]
struct NearestNeighbourComponent;
#[derive(Component)]
struct Cursor;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(AutomaticUpdate::<NearestNeighbourComponent>::new())
.add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin::default())
.insert_resource(Mouse3D { pos: Vec3::ZERO })
.add_systems(Startup, setup)
.add_systems(
Update,
(update_mouse_pos, (mouse, color, reset_color.before(color))).chain(),
)
.run();
}
#[derive(Resource, Clone)]
struct MaterialHandles {
orange_red: Handle<StandardMaterial>,
black: Handle<StandardMaterial>,
blue: Handle<StandardMaterial>,
}
type NNTree = KDTree3<NearestNeighbourComponent>;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut ambient_light: ResMut<AmbientLight>,
) {
let handles = MaterialHandles {
orange_red: materials.add(Color::from(csscolors::ORANGE_RED)),
black: materials.add(Color::from(csscolors::BLACK)),
blue: materials.add(Color::from(csscolors::BLUE)),
};
commands.insert_resource(handles.clone());
ambient_light.color = Color::WHITE;
ambient_light.brightness = 500.;
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 100.0, 900.0).looking_at(Vec3::ZERO, Vec3::Y),
));
commands
.spawn((
Mesh3d(meshes.add(Cuboid::new(10., 10., 10.))),
MeshMaterial3d(handles.blue.clone()),
Transform::from_xyz(0.0, 0.5, 0.0),
))
.insert(Cursor);
let mesh = meshes.add(Cuboid::new(4., 4., 4.));
for x in -25..25 {
for y in -25..25 {
for z in -9..9 {
commands.spawn((
Mesh3d(mesh.clone()),
MeshMaterial3d(handles.orange_red.clone()),
Transform::from_xyz((x * 15) as f32, (y * 15) as f32, (z * 15) as f32),
NearestNeighbourComponent,
));
}
}
}
}
#[derive(Copy, Clone, Resource)]
struct Mouse3D {
pos: Vec3,
}
fn update_mouse_pos(
window: Single<&Window, With<PrimaryWindow>>,
camera: Single<(&Camera, &GlobalTransform)>,
mut mouse: ResMut<Mouse3D>,
) {
let (cam, cam_t) = camera.deref();
if let Some(w_pos) = window.cursor_position() {
if let Ok(pos) = cam.viewport_to_world(cam_t, w_pos) {
mouse.pos = pos.get_point(900.);
}
}
}
fn mouse(mouse: Res<Mouse3D>, mut transform: Single<&mut Transform, With<Cursor>>) {
transform.translation = mouse.pos;
}
fn color(
mouse: Res<Mouse3D>,
treeaccess: Res<NNTree>,
mut query: Query<&mut MeshMaterial3d<StandardMaterial>, With<NearestNeighbourComponent>>,
colors: Res<MaterialHandles>,
) {
for (_, entity) in treeaccess.within_distance(mouse.pos, 100.0) {
if let Ok(mut handle) = query.get_mut(entity.expect("No entity")) {
*handle = colors.black.clone().into();
}
}
}
fn reset_color(
colors: Res<MaterialHandles>,
mut query: Query<&mut MeshMaterial3d<StandardMaterial>, With<NearestNeighbourComponent>>,
) {
for mut handle in &mut query {
*handle = colors.orange_red.clone().into();
}
}

View File

@@ -0,0 +1,112 @@
use std::time::Duration;
use bevy::color::palettes::css as csscolors;
use bevy::prelude::*;
use bevy_spatial::{
AutomaticUpdate, SpatialAccess, SpatialStructure, TimestepLength, kdtree::KDTree2,
};
#[derive(Component, Default)]
struct NearestNeighbour;
#[derive(Component)]
struct Chaser;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(
AutomaticUpdate::<NearestNeighbour>::new()
.with_frequency(Duration::from_millis(305))
.with_spatial_ds(SpatialStructure::KDTree2),
)
.add_systems(Startup, setup)
.add_systems(Update, (move_to, rotate_around, mouseclick))
.run();
}
type NNTree = KDTree2<NearestNeighbour>;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands.spawn((
Text("Click mouse to change rate".to_string()),
TextFont {
font_size: 30.0,
..default()
},
TextColor(Color::BLACK),
));
commands.spawn((
Chaser,
Sprite {
color: csscolors::BLUE.into(),
custom_size: Some(Vec2::new(10.0, 10.0)),
..default()
},
Transform::from_translation(Vec3::ZERO),
));
let neighbours = [
(csscolors::RED, Vec3::Y * 100.),
(csscolors::RED, Vec3::NEG_Y * 100.),
(csscolors::RED, Vec3::X * 100.),
(csscolors::RED, Vec3::NEG_X * 100.),
];
for (color, position) in neighbours {
commands.spawn((
NearestNeighbour,
Sprite {
color: Color::from(color),
custom_size: Some(Vec2::new(10.0, 10.0)),
..default()
},
Transform::from_translation(position),
));
}
}
fn rotate_around(mut query: Query<&mut Transform, With<NearestNeighbour>>) {
for mut transform in &mut query {
transform.rotate_around(
Vec3::ZERO,
Quat::from_axis_angle(Vec3::Z, 1.0f32.to_radians()),
);
}
}
fn move_to(
treeaccess: Res<NNTree>,
time: Res<Time>,
mut query: Query<&mut Transform, With<Chaser>>,
) {
for mut transform in &mut query {
if let Some(nearest) = treeaccess.nearest_neighbour(transform.translation.truncate()) {
let towards = nearest.0.extend(0.0) - transform.translation;
transform.translation += towards.normalize() * time.delta_secs() * 350.0;
}
}
}
/// Change the timestep for
fn mouseclick(
mouse_input: Res<ButtonInput<MouseButton>>,
mut text: Single<&mut Text>,
mut step: ResMut<TimestepLength<NearestNeighbour>>,
mut other_duration: Local<Duration>,
) {
if other_duration.is_zero() {
*other_duration = Duration::from_millis(1);
}
if mouse_input.just_pressed(MouseButton::Left) {
let duration = step.get_duration();
step.set_duration(*other_duration);
text.0 = format!("Spatial Update Rate: {} ms", other_duration.as_millis());
*other_duration = duration;
}
}

View File

@@ -0,0 +1,115 @@
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
math::vec2,
prelude::*,
time::common_conditions::on_timer,
window::PrimaryWindow,
};
use bevy_spatial::{
AutomaticUpdate, SpatialAccess, SpatialStructure, TransformMode, kdtree::KDTree3,
};
use std::ops::Deref;
use std::time::Duration;
#[derive(Component, Default)]
struct NearestNeighbour;
#[derive(Component)]
struct MoveTowards;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(
AutomaticUpdate::<NearestNeighbour>::new()
.with_spatial_ds(SpatialStructure::KDTree3)
.with_frequency(Duration::from_secs(1))
.with_transform(TransformMode::Transform),
)
.add_plugins(FrameTimeDiagnosticsPlugin::default())
.add_plugins(LogDiagnosticsPlugin::default())
.add_systems(Startup, setup)
.add_systems(
Update,
(
mouseclick,
log_num.run_if(on_timer(Duration::from_secs_f32(0.5))),
move_to,
),
)
.run();
}
type NNTree = KDTree3<NearestNeighbour>;
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
for x in -6..6 {
for y in -6..6 {
commands.spawn((
NearestNeighbour,
Sprite {
color: Color::srgb(0.7, 0.3, 0.5),
custom_size: Some(Vec2::new(10.0, 10.0)),
..default()
},
Transform {
translation: Vec3::new((x * 100) as f32, (y * 100) as f32, 0.0),
..default()
},
));
}
}
}
fn mouseclick(
mut commands: Commands,
mouse_input: Res<ButtonInput<MouseButton>>,
window: Single<&Window, With<PrimaryWindow>>,
camera: Single<(&Camera, &GlobalTransform)>,
) {
let (cam, cam_t) = camera.deref();
if mouse_input.pressed(MouseButton::Left) {
if let Some(pos) = window.cursor_position() {
for xoff in -1..=1 {
for yoff in -1..=1 {
commands.spawn((
MoveTowards,
Sprite {
color: Color::srgb(0.15, 0.15, 1.0),
custom_size: Some(Vec2::new(10.0, 10.0)),
..default()
},
Transform {
translation: cam
.viewport_to_world_2d(
cam_t,
pos + vec2(xoff as f32 * 16., yoff as f32 * 16.),
)
.unwrap_or(Vec2::ZERO)
.extend(1.0),
..default()
},
));
}
}
}
}
}
fn move_to(
treeaccess: Res<NNTree>,
time: Res<Time>,
mut query: Query<&mut Transform, With<MoveTowards>>,
) {
for mut transform in &mut query {
if let Some(nearest) = treeaccess.nearest_neighbour(transform.translation) {
let towards = nearest.0 - transform.translation;
transform.translation += towards.normalize() * time.delta_secs() * 64.0;
}
}
}
fn log_num(query: Query<(), With<MoveTowards>>) {
info!(target: "blue", amount = query.iter().len())
}

View File

@@ -0,0 +1,89 @@
use std::marker::PhantomData;
use crate::{
SpatialAccess,
point::{SpatialPoint, VecFromGlobalTransform, VecFromTransform},
spatial_access::UpdateSpatialAccess,
};
use bevy::{
ecs::schedule::{ScheduleLabel, SystemSet},
prelude::*,
};
/// Select which Transform to use when automatically updating the Spatial Datastructure.
#[derive(Clone, Default, Copy)]
pub enum TransformMode {
/// Uses the normal [`Transform`] for updating the Spatial Datastructure.
#[default]
Transform,
/// Uses the [`GlobalTransform`] for updating the Spatial Datastructure.
GlobalTransform,
}
// long term todo: add support for CustomCoordinate mode which uses a user-defined type implementing a trait like VecFromTransform
type GlamVec<S> = <<S as SpatialAccess>::Point as SpatialPoint>::Vec;
pub(crate) struct AutoT<SpatialDS>(PhantomData<SpatialDS>);
impl<SpatialDS> AutoT<SpatialDS>
where
GlamVec<SpatialDS>: VecFromTransform,
SpatialDS: UpdateSpatialAccess + Resource,
<SpatialDS as SpatialAccess>::Point: From<(Entity, GlamVec<SpatialDS>)>,
SpatialDS::Comp: Component,
{
#[allow(clippy::needless_pass_by_value)]
fn update_ds(
mut tree: ResMut<SpatialDS>,
changed: Query<(Entity, Ref<Transform>), With<SpatialDS::Comp>>,
mut removed: RemovedComponents<SpatialDS::Comp>,
) {
tree.update(
changed.iter().map(|(e, ch)| {
let changed = ch.is_changed();
(
(e, GlamVec::<SpatialDS>::from_transform(ch.into_inner())).into(),
changed,
)
}),
removed.read(),
);
}
pub fn build(app: &mut App, schedule: impl ScheduleLabel, set: impl SystemSet) {
app.add_systems(schedule, Self::update_ds.in_set(set));
}
}
pub(crate) struct AutoGT<SpatialDS>(PhantomData<SpatialDS>);
impl<SpatialDS> AutoGT<SpatialDS>
where
GlamVec<SpatialDS>: VecFromGlobalTransform,
SpatialDS: UpdateSpatialAccess + Resource,
<SpatialDS as SpatialAccess>::Point: From<(Entity, GlamVec<SpatialDS>)>,
SpatialDS::Comp: Component,
{
#[allow(clippy::needless_pass_by_value)]
fn update_ds(
mut tree: ResMut<SpatialDS>,
changed: Query<(Entity, Ref<GlobalTransform>), With<SpatialDS::Comp>>,
mut removed: RemovedComponents<SpatialDS::Comp>,
) {
tree.update(
changed.iter().map(|(e, ch)| {
let changed = ch.is_changed();
(
(e, GlamVec::<SpatialDS>::from_transform(ch.into_inner())).into(),
changed,
)
}),
removed.read(),
);
}
pub fn build(app: &mut App, schedule: impl ScheduleLabel, set: impl SystemSet) {
app.add_systems(schedule, Self::update_ds.in_set(set));
}
}

139
vendor/bevy_spatial/src/kdtree.rs vendored Normal file
View File

@@ -0,0 +1,139 @@
//! implementations to use [`kd_tree`] trees as a spatial datastructure in ``bevy_spatial``.
use bevy::prelude::*;
use kd_tree::{KdPoint, KdTree as BaseKdTree, KdTreeN};
use crate::{
TComp,
point::SpatialPoint,
spatial_access::{SpatialAccess, UpdateSpatialAccess},
};
use std::marker::PhantomData;
use bevy::prelude::Resource;
#[cfg(all(feature = "kdtree_rayon", target_arch = "wasm32"))]
compile_error!(
"bevy-spatial feature \"kdtree_rayon\" is incompatible with target_arch = \"wasm32\" builds. Disable default-features and enable kdtree"
);
macro_rules! kdtree_impl {
($pt:ty, $treename:ident) => {
impl KdPoint for $pt {
type Scalar = <$pt as SpatialPoint>::Scalar;
type Dim = <$pt as SpatialPoint>::Dimension;
fn at(&self, i: usize) -> Self::Scalar {
<Self as SpatialPoint>::at(self, i)
}
}
/// Resource for storing a ``KdTree``
#[derive(Resource)]
pub struct $treename<Comp> {
/// The ``KdTree``
pub tree: BaseKdTree<$pt>,
component_type: PhantomData<Comp>,
}
impl<Comp> Default for $treename<Comp> {
fn default() -> Self {
Self {
tree: default(),
component_type: PhantomData,
}
}
}
impl<Comp> SpatialAccess for $treename<Comp>
where
Comp: TComp,
{
type Point = $pt;
type Comp = Comp;
type ResultT = (<$pt as SpatialPoint>::Vec, Option<Entity>);
/// Get the nearest neighbour to a position.
fn nearest_neighbour(&self, loc: <$pt as SpatialPoint>::Vec) -> Option<Self::ResultT> {
let p: $pt = loc.into();
let res = self.tree.nearest(&p);
res.map(|point| (point.item.vec(), point.item.entity()))
}
/// Get the `k` neighbours to `loc`
///
/// If `loc` is the location of a tracked entity, you might want to skip the first.
fn k_nearest_neighbour(
&self,
loc: <$pt as SpatialPoint>::Vec,
k: usize,
) -> Vec<Self::ResultT> {
let p: $pt = loc.into();
self.tree
.nearests(&p, k)
.iter()
.map(|e| (e.item.vec(), e.item.entity()))
.collect()
}
/// Get all entities within a certain distance (radius) of `loc`
fn within_distance(
&self,
loc: <$pt as SpatialPoint>::Vec,
distance: <$pt as SpatialPoint>::Scalar,
) -> Vec<Self::ResultT> {
let distance: <$pt as KdPoint>::Scalar = distance.into();
if self.tree.len() == 0 {
vec![]
} else {
let p: $pt = loc.into();
self.tree
.within_radius(&p, distance)
.iter()
.map(|e| (e.vec(), e.entity()))
.collect()
}
}
}
impl<Comp: TComp> UpdateSpatialAccess for $treename<Comp> {
fn update(
&mut self,
data: impl Iterator<Item = (Self::Point, bool)>,
_: impl Iterator<Item = Entity>,
) {
#[cfg(all(feature = "kdtree_rayon", not(target_arch = "wasm32")))]
let tree =
KdTreeN::par_build_by_ordered_float(data.map(|(p, _)| p).collect::<Vec<_>>());
#[cfg(any(not(feature = "kdtree_rayon"), target_arch = "wasm32"))]
let tree =
KdTreeN::build_by_ordered_float(data.map(|(p, _)| p).collect::<Vec<_>>());
self.tree = tree;
}
fn add(&mut self, _: Self::Point) {}
fn remove_point(&mut self, _: Self::Point) -> bool {
false
}
fn remove_entity(&mut self, _: Entity) -> bool {
false
}
fn clear(&mut self) {
self.tree = KdTreeN::default();
}
}
};
}
kdtree_impl!(crate::point::Point2, KDTree2);
kdtree_impl!(crate::point::Point3, KDTree3);
kdtree_impl!(crate::point::Point3A, KDTree3A);
kdtree_impl!(crate::point::PointD2, KDTreeD2);
kdtree_impl!(crate::point::PointD3, KDTreeD3);

54
vendor/bevy_spatial/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,54 @@
#![warn(missing_docs)]
#![deny(clippy::pedantic)]
//! A bevy plugin to track your entities in spatial indices and query them.
//!
//! Quickstart using the `kdtree` feature:
//! ```
//! use bevy_spatial::{AutomaticUpdate, KDTree3, TransformMode, SpatialAccess};
//!
//! #[derive(Component, Default)]
//! struct TrackedByKDTree;
//!
//! fn main() {
//! App::new()
//! .add_plugin(AutomaticUpdate::<TrackedByKDTree>::new()
//! .with_frequency(Duration::from_secs_f32(0.3))
//! .with_transform(TransformMode::GlobalTransform))
//! .add_system(use_neighbour);
//! // ...
//! }
//!
//! type NNTree = KDTree3<TrackedByKDTree>; // type alias for later
//!
//! // spawn some entities with the TrackedByKDTree component
//!
//! fn use_neighbour(tree: Res<NNTree>){
//! if let Some((pos, entity)) = tree.nearest_neighbour(Vec3::ZERO) {
//! // pos: Vec3
//! // do something with the nearest entity here
//! }
//! }
//! ```
//!
//! For more details see [Examples](https://github.com/laundmo/bevy-spatial/tree/main/examples)
pub mod point;
mod spatial_access;
pub use self::spatial_access::SpatialAccess;
use bevy::prelude::Component;
mod timestep;
pub use self::timestep::TimestepLength;
pub mod kdtree;
mod plugin;
pub use plugin::{SpatialStructure, *};
mod automatic_systems;
pub use automatic_systems::TransformMode;
/// automatically implemented trait for all components which can be used as markers for automatic updates?
pub trait TComp: Component + Send + Sync + 'static {}
impl<T> TComp for T where T: Component + Send + Sync + 'static {}

185
vendor/bevy_spatial/src/plugin.rs vendored Normal file
View File

@@ -0,0 +1,185 @@
use std::{marker::PhantomData, time::Duration};
use bevy::{
ecs::schedule::{ScheduleLabel, SystemSet},
prelude::*,
};
use crate::{
TComp,
automatic_systems::{AutoGT, AutoT, TransformMode},
kdtree::{KDTree2, KDTree3, KDTree3A},
timestep::{TimestepLength, on_timer_changeable},
};
/// Default set for spatial datastructure updates. Can be overridden using [`AutomaticUpdate::with_set()`](crate::AutomaticUpdate)
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub struct SpatialSet;
/// Enum containing the different types of spatial datastructure compatible with [`AutomaticUpdate`]
#[derive(Copy, Clone, Default)]
pub enum SpatialStructure {
/// Corresponds to [`kdtree::KdTree2`](crate::kdtree::KDTree2)
KDTree2,
/// Corresponds to [`kdtree::KdTree3`](crate::kdtree::KDTree3)
#[default]
KDTree3,
/// Corresponds to [`kdtree::KdTree3A`](crate::kdtree::KDTree3A)
KDTree3A,
// Linear/naive (linfa?)
// Grid
// RStar
}
/// Plugin struct for setting up a spatial datastructure with automatic updating.
///
///
/// ```
/// #[derive(Component, Default)]
/// struct EntityMarker;
///
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_plugin(AutomaticUpdate::<EntityMarker>::new()
/// .with_frequency(Duration::from_secs_f32(0.3))
/// .with_spatial_ds(SpatialStructure::KDTree2)
/// .with_transform(TransformMode::GlobalTransform)
/// )
///
/// ```
pub struct AutomaticUpdate<Comp, Set = SpatialSet, Schedule = Update>
where
Set: SystemSet,
Schedule: ScheduleLabel + Clone,
{
pub(crate) comp: PhantomData<Comp>,
pub(crate) set: Set,
pub(crate) schedule: Schedule,
pub(crate) frequency: Duration,
pub(crate) transform: TransformMode,
pub(crate) spatial_ds: SpatialStructure,
}
impl<Comp, Set: SystemSet, Schedule: ScheduleLabel + Clone> AutomaticUpdate<Comp, Set, Schedule> {
/// Create a new [`AutomaticUpdate`] with defaults. Will add to the default [`ScheduleLabel`]: [`Update`].
#[must_use]
pub fn new() -> AutomaticUpdate<Comp> {
AutomaticUpdate {
comp: PhantomData,
set: SpatialSet,
schedule: Update,
frequency: Duration::from_millis(50),
transform: TransformMode::Transform,
spatial_ds: default(),
}
}
/// Change the Bevy [`ScheduleLabel`] in which this plugin will put its systems.
pub fn with_schedule<NewSchedule: ScheduleLabel + Clone>(
self,
schedule: NewSchedule,
) -> AutomaticUpdate<Comp, Set, NewSchedule> {
// Struct filling for differing types is experimental. Have to manually list each.
AutomaticUpdate {
set: self.set,
schedule,
comp: PhantomData,
frequency: self.frequency,
transform: self.transform,
spatial_ds: self.spatial_ds,
}
}
/// Change the Bevy [`SystemSet`] in which this plugin will put its systems.
pub fn with_set<NewSet: SystemSet + Copy>(
self,
set: NewSet,
) -> AutomaticUpdate<Comp, NewSet, Schedule> {
// Struct filling for differing types is experimental. Have to manually list each.
AutomaticUpdate::<Comp, NewSet, Schedule> {
set,
schedule: self.schedule,
comp: PhantomData,
frequency: self.frequency,
transform: self.transform,
spatial_ds: self.spatial_ds,
}
}
/// Change which spatial datastructure is used.
///
/// expects one of:
/// - [`SpatialStructure::KDTree2`]
/// - [`SpatialStructure::KDTree3`] (default)
/// - [`SpatialStructure::KDTree3A`]
#[must_use]
pub fn with_spatial_ds(self, spatial_ds: SpatialStructure) -> Self {
Self { spatial_ds, ..self }
}
/// Change the update rate.
///
/// Expects a [Duration] which is the delay between updates.
#[must_use]
pub fn with_frequency(self, frequency: Duration) -> Self {
Self { frequency, ..self }
}
/// Change which Transform is used to extrat coordinates from.
///
/// - [`TransformMode::Transform`] (default)
/// - [`TransformMode::GlobalTransform`]
///
/// Note: using [`TransformMode::GlobalTransform`] might cause double frame-delays
/// as Transform->GlobalTransform propagation happens in the
/// [`TransformPropagate`](bevy::transform::TransformSystem::TransformPropagate) [`SystemSet`] in [`PostUpdate`](bevy::app::PostUpdate).
/// You can order this plugins systems by modifying the default [`SpatialSet`]
/// or using your own [`SystemSet`] by calling [`AutomaticUpdate::with_set`](Self::with_set)
#[must_use]
pub fn with_transform(self, transform: TransformMode) -> Self {
Self { transform, ..self }
}
}
impl<Comp: TComp, Set: SystemSet + Copy, Schedule: ScheduleLabel + Clone> Plugin
for AutomaticUpdate<Comp, Set, Schedule>
{
fn build(&self, app: &mut App) {
app.insert_resource(TimestepLength(self.frequency, PhantomData::<Comp>))
.configure_sets(
self.schedule.clone(),
self.set.run_if(on_timer_changeable::<Comp>),
);
match self.spatial_ds {
SpatialStructure::KDTree2 => app.init_resource::<KDTree2<Comp>>(),
SpatialStructure::KDTree3 => app.init_resource::<KDTree3<Comp>>(),
SpatialStructure::KDTree3A => app.init_resource::<KDTree3A<Comp>>(),
};
match self.transform {
TransformMode::Transform => match self.spatial_ds {
SpatialStructure::KDTree2 => {
AutoT::<KDTree2<Comp>>::build(app, self.schedule.clone(), self.set);
}
SpatialStructure::KDTree3 => {
AutoT::<KDTree3<Comp>>::build(app, self.schedule.clone(), self.set);
}
SpatialStructure::KDTree3A => {
AutoT::<KDTree3A<Comp>>::build(app, self.schedule.clone(), self.set);
}
},
TransformMode::GlobalTransform => match self.spatial_ds {
SpatialStructure::KDTree2 => {
AutoGT::<KDTree2<Comp>>::build(app, self.schedule.clone(), self.set);
}
SpatialStructure::KDTree3 => {
AutoGT::<KDTree3<Comp>>::build(app, self.schedule.clone(), self.set);
}
SpatialStructure::KDTree3A => {
AutoGT::<KDTree3A<Comp>>::build(app, self.schedule.clone(), self.set);
}
},
}
}
}

206
vendor/bevy_spatial/src/point.rs vendored Normal file
View File

@@ -0,0 +1,206 @@
//! The different point Traits and Types used by ``bevy_spatial``
//!
//! - [`Scalar`] is a Trait based on [`num_traits`] which is implemented for all numeric types used in Points.
//! - [`SpatialPoint`] is a Trait that represents a point in space and the Entity it was created from.
//! It defines some, common methods needed while working with these points in different spatial datastructures.
//! - [`IntoSpatialPoint`] is a Trait which is implemented for vector coordinate types for which a corresponding Point type exists.
//! Needs a [`Entity`] to include in the Point type.
//! - [`VecFromTransform`] and [`VecFromGlobalTransform`] used to extract the translation from the corresponding Transform.
//! Used for automatically updating the spatial datastructure.
use bevy::{math::Vec3A, prelude::*};
use num_traits::{Bounded, Num, Signed};
use std::fmt::Debug;
use typenum::Unsigned;
/// Trait implemented for all numeric types used in Points.
pub trait Scalar: Bounded + Num + Clone + Copy + Signed + PartialOrd + Debug {}
impl<T> Scalar for T where T: Bounded + Num + Clone + Copy + Signed + PartialOrd + Debug {}
/// Represents a point in space and the Entity it was created from.
///
/// Implements a bunch of common methods needed while working with these points in different spatial datastructures.
#[allow(clippy::module_name_repetitions)]
pub trait SpatialPoint: Copy + Clone + PartialEq + Debug {
/// The Scalar type of a vector, example: [`f32`], [`f64`]
type Scalar: Scalar;
/// The vector type itself, for example [`Vec3`]
type Vec: Send + Sync + IntoSpatialPoint;
/// The dimension of this vector, like [`typenum::U2`] [`typenum::U3`]
type Dimension: Unsigned;
/// Get the value at this index.
/// Used for datastructure specific implementations.
///
/// `nth` is always smaller than [`Self::Dimension`].
fn at(&self, nth: usize) -> Self::Scalar;
/// Get the squared distance of this point to another point of the same type.
fn distance_squared(&self, other: &Self) -> Self::Scalar;
/// Get the elementwise minimum between this and another point
fn min_point(&self, other: &Self) -> Self::Vec;
/// Get the elementwise maximum between this and another point
fn max_point(&self, other: &Self) -> Self::Vec;
/// Get the Entity associated with this point.
fn entity(&self) -> Option<Entity>;
/// Get a this points vector.
fn vec(&self) -> Self::Vec;
}
/// Trait implemented for vector coordinate types for which a corresponding Point type exists.
/// Used to convert from the vector coordinate types to the corresponding Point type by providing a Entity
#[allow(clippy::module_name_repetitions)]
pub trait IntoSpatialPoint: Send + Sync + Sized + Copy {
/// The resulting point type, for example [`Point3`]
type Point: SpatialPoint + From<(Entity, Self)> + Copy;
/// Converts from the implementing type to the point type with its Entity filled.
fn into_spatial_point(self, e: Entity) -> Self::Point
where
Self::Point: From<(Entity, Self)>,
{
(e, self).into()
}
}
macro_rules! impl_spatial_point {
($pointname:ident, $bvec:ty, $unit:ty, $dim:ty, $diml:literal) => {
/// Newtype over bevy/glam vectors, needed to allow implementing foreign spatial datastructure traits.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct $pointname {
/// The vector of this Point
pub vec: $bvec,
/// The Entity associated with this Point
pub entity: Option<Entity>,
}
impl $pointname {
fn new(vec: $bvec, entity: Entity) -> Self {
$pointname {
vec,
entity: Some(entity),
}
}
fn from_vec(vec: $bvec) -> Self {
$pointname { vec, entity: None }
}
}
impl SpatialPoint for $pointname {
type Scalar = $unit;
type Vec = $bvec;
type Dimension = $dim;
#[inline]
fn at(&self, nth: usize) -> Self::Scalar {
self.vec[nth]
}
#[inline]
fn distance_squared(&self, other: &Self) -> Self::Scalar {
self.vec.distance_squared(other.vec)
}
#[inline]
fn min_point(&self, other: &Self) -> Self::Vec {
self.vec.min(other.vec)
}
#[inline]
fn max_point(&self, other: &Self) -> Self::Vec {
self.vec.max(other.vec)
}
#[inline]
fn entity(&self) -> Option<Entity> {
self.entity
}
#[inline]
fn vec(&self) -> Self::Vec {
self.vec
}
}
impl From<(Entity, $bvec)> for $pointname {
fn from(value: (Entity, $bvec)) -> Self {
$pointname::new(value.1, value.0)
}
}
impl From<($bvec, Entity)> for $pointname {
fn from(value: ($bvec, Entity)) -> Self {
$pointname::new(value.0, value.1)
}
}
impl From<$bvec> for $pointname {
fn from(value: $bvec) -> Self {
$pointname::from_vec(value)
}
}
impl IntoSpatialPoint for $bvec {
type Point = $pointname;
}
};
}
impl_spatial_point!(Point2, bevy::math::Vec2, f32, typenum::consts::U2, 2);
impl_spatial_point!(Point3, bevy::math::Vec3, f32, typenum::consts::U3, 3);
impl_spatial_point!(Point3A, bevy::math::Vec3A, f32, typenum::consts::U3, 3);
impl_spatial_point!(PointD2, bevy::math::DVec2, f64, typenum::consts::U2, 2);
impl_spatial_point!(PointD3, bevy::math::DVec3, f64, typenum::consts::U3, 3);
/// Helper trait for extracting the translation of a [`Transform`] to a specific vector type
/// Used for automatically updating the spatial datastructure.
pub trait VecFromTransform: IntoSpatialPoint {
/// Create this vector type from a [`Transform`]
fn from_transform(t: &Transform) -> Self;
}
impl VecFromTransform for Vec2 {
fn from_transform(t: &Transform) -> Self {
t.translation.truncate()
}
}
impl VecFromTransform for Vec3 {
fn from_transform(t: &Transform) -> Self {
t.translation
}
}
impl VecFromTransform for Vec3A {
fn from_transform(t: &Transform) -> Self {
t.translation.into()
}
}
/// Helper trait for extracting the translation of a [`GlobalTransform`] to a specific vector type
/// Used for automatically updating the spatial datastructure.
pub trait VecFromGlobalTransform: IntoSpatialPoint {
/// Create this vector type from a [`GlobalTransform`]
fn from_transform(t: &GlobalTransform) -> Self;
}
impl VecFromGlobalTransform for Vec2 {
fn from_transform(t: &GlobalTransform) -> Self {
t.translation().truncate()
}
}
impl VecFromGlobalTransform for Vec3 {
fn from_transform(t: &GlobalTransform) -> Self {
t.translation()
}
}
impl VecFromGlobalTransform for Vec3A {
fn from_transform(t: &GlobalTransform) -> Self {
t.translation().into()
}
}

View File

@@ -0,0 +1,66 @@
use bevy::prelude::*;
use crate::{TComp, point::SpatialPoint};
// todo: change Point to impl IntoPoint?
#[allow(clippy::module_name_repetitions)]
pub trait UpdateSpatialAccess: SpatialAccess {
/// Updates the underlying datastructure
///
/// The boolean indicates if the point needs to be updated or is a existing point.
/// data should always include all points, even if they are not updated.
/// This is for datastructures like ``KDTree``, which need to be fully rebuilt.
fn update(
&mut self,
data: impl Iterator<Item = (Self::Point, bool)>,
removed: impl Iterator<Item = Entity>,
) {
for (p, changed) in data {
if changed {
self.remove_point(p);
self.add(p);
}
}
for e in removed {
self.remove_entity(e);
}
}
/// Adds the point to the underlying datastructure.
fn add(&mut self, point: Self::Point);
/// Remove the point by coordinate + entity from the underlying datastructure.
fn remove_point(&mut self, point: Self::Point) -> bool;
/// Remove the point by entity from the underlying datastructure.
fn remove_entity(&mut self, entity: Entity) -> bool;
/// Clear the underlying datastructure, removing all points it contains.
fn clear(&mut self);
}
/// Trait for accessing point-based spatial datastructures.
pub trait SpatialAccess: Send + Sync + 'static {
/// The point type, can be anything implementing [`SpatialPoint`].
type Point: SpatialPoint;
/// The marker component type marking the entities whos points are stored, used for accessing the component in trait bounds.
type Comp: TComp;
/// The type of a single query result.
type ResultT;
/// Get the nearest neighbour to `loc`.
/// Be aware that that distance to the returned point will be zero if `loc` is part of the datastructure.
fn nearest_neighbour(&self, loc: <Self::Point as SpatialPoint>::Vec) -> Option<Self::ResultT>;
/// Return the k nearest neighbours to `loc`.
fn k_nearest_neighbour(
&self,
loc: <Self::Point as SpatialPoint>::Vec,
k: usize,
) -> Vec<Self::ResultT>;
/// Return all points which are within the specified distance.
fn within_distance(
&self,
loc: <Self::Point as SpatialPoint>::Vec,
distance: <Self::Point as SpatialPoint>::Scalar,
) -> Vec<Self::ResultT>;
}
// TODO: SpatialAABBAccess trait definition - should it be separate from SpatialAccess or depend on it?

55
vendor/bevy_spatial/src/timestep.rs vendored Normal file
View File

@@ -0,0 +1,55 @@
use std::{marker::PhantomData, time::Duration};
use bevy::{
prelude::{Local, Res, Resource},
time::{Time, Timer, TimerMode},
};
use crate::TComp;
/// Resource used for fixed timestep without repeats in the same frame (builtin timestep may run the system multiple times per frame).
///
/// To modify the timestep at runtime, a system like this can be used:
/// ```rust
/// fn update_timestep(
/// mut step: ResMut<TimestepElapsed<NearestNeighbourMarker>>,
/// ) {
/// if some_condition {
/// step.set_duration(Duration::from_millis(15)); // only update spatial datastructure every 15ms.
/// }
/// }
/// ```
/// `NearestNeighbourMarker` in this case refers to the (marker) component you also passed to the Plugin.
#[allow(clippy::module_name_repetitions)]
#[derive(Resource, Default)]
pub struct TimestepLength<Comp>(pub Duration, pub(crate) PhantomData<Comp>);
impl<Comp> TimestepLength<Comp> {
/// Set the length of the timestep.
pub fn set_duration(&mut self, duration: Duration) {
self.0 = duration;
}
/// Get the length of the timestep.
#[must_use]
pub fn get_duration(&self) -> Duration {
self.0
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn on_timer_changeable<Comp>(
length: Res<TimestepLength<Comp>>,
time: Res<Time>,
mut timer: Local<Timer>,
) -> bool
where
Comp: TComp,
{
if length.get_duration() != timer.duration() {
timer.set_mode(TimerMode::Repeating);
timer.set_duration(length.get_duration());
}
timer.tick(time.delta());
timer.just_finished()
}