Convert flocking systems to use Avian2d
This commit is contained in:
@@ -2,9 +2,6 @@ pub mod physics;
|
|||||||
|
|
||||||
use avian2d::prelude::*;
|
use avian2d::prelude::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_spatial::{
|
|
||||||
AutomaticUpdate, SpatialAccess, SpatialStructure, TransformMode, kdtree::KDTree2,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::birdoids::physics::{Force, Velocity, apply_velocity};
|
use crate::birdoids::physics::{Force, Velocity, apply_velocity};
|
||||||
use bevy_inspector_egui::{InspectorOptions, prelude::ReflectInspectorOptions};
|
use bevy_inspector_egui::{InspectorOptions, prelude::ReflectInspectorOptions};
|
||||||
@@ -15,29 +12,23 @@ pub struct BoidsPlugin;
|
|||||||
|
|
||||||
impl Plugin for BoidsPlugin {
|
impl Plugin for BoidsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins(
|
app.insert_resource(ClearColor(BACKGROUND_COLOR))
|
||||||
AutomaticUpdate::<TrackedByKdTree>::new()
|
.insert_resource(FlockingParameters::new())
|
||||||
// .with_frequency(Duration::from_secs_f32(0.3))
|
.register_type::<FlockingParameters>()
|
||||||
.with_transform(TransformMode::GlobalTransform)
|
.insert_resource(MiscParams::new())
|
||||||
.with_spatial_ds(SpatialStructure::KDTree2),
|
.register_type::<MiscParams>()
|
||||||
)
|
.add_systems(Startup, (spawn_camera, spawn_boids))
|
||||||
.insert_resource(ClearColor(BACKGROUND_COLOR))
|
.add_systems(
|
||||||
.insert_resource(FlockingParameters::new())
|
FixedUpdate,
|
||||||
.register_type::<FlockingParameters>()
|
(
|
||||||
.insert_resource(MiscParams::new())
|
apply_velocity,
|
||||||
.register_type::<MiscParams>()
|
turn_if_edge,
|
||||||
.add_systems(Startup, (spawn_camera, spawn_boids))
|
cohesion,
|
||||||
.add_systems(
|
separation,
|
||||||
FixedUpdate,
|
alignment,
|
||||||
(
|
speed_controller,
|
||||||
apply_velocity,
|
),
|
||||||
turn_if_edge,
|
);
|
||||||
cohesion,
|
|
||||||
separation,
|
|
||||||
alignment,
|
|
||||||
speed_controller,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +73,9 @@ impl MiscParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[require(Velocity, Force, TrackedByKdTree)]
|
#[require(Velocity, Force)]
|
||||||
pub(crate) struct Boid;
|
pub(crate) struct Boid;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
|
||||||
pub struct TrackedByKdTree;
|
|
||||||
|
|
||||||
fn spawn_camera(mut commands: Commands) {
|
fn spawn_camera(mut commands: Commands) {
|
||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
}
|
}
|
||||||
@@ -153,7 +141,9 @@ fn turn_if_edge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cohesion(
|
fn cohesion(
|
||||||
spatial_tree: Res<KDTree2<TrackedByKdTree>>,
|
spatial: SpatialQuery,
|
||||||
|
// TODO: Ensure this is logically sound. I think it will fail the "disjoint queries" requirement.
|
||||||
|
boid_locations: Query<&Transform, With<Boid>>,
|
||||||
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
||||||
props: Res<FlockingParameters>,
|
props: Res<FlockingParameters>,
|
||||||
) {
|
) {
|
||||||
@@ -163,14 +153,23 @@ fn cohesion(
|
|||||||
// find vector from boid to flock CoM
|
// find vector from boid to flock CoM
|
||||||
// apply force
|
// apply force
|
||||||
for (this_entt, transform, mut force) in &mut boids {
|
for (this_entt, transform, mut force) in &mut boids {
|
||||||
let (len, sum) = spatial_tree
|
let (len, sum) = spatial
|
||||||
.within_distance(transform.translation.xy(), props.view_range)
|
.shape_intersections(
|
||||||
|
&Collider::circle(props.view_range),
|
||||||
|
transform.translation.xy(),
|
||||||
|
0.0,
|
||||||
|
&SpatialQueryFilter::default(),
|
||||||
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(pos, entt)| {
|
.filter_map(|&entt| {
|
||||||
|
// extract neighbor's position
|
||||||
// Skip self-comparison. A boid should not try to separate from itself.
|
// Skip self-comparison. A boid should not try to separate from itself.
|
||||||
let entt = entt
|
if this_entt == entt {
|
||||||
.expect("within_distance gave me an entity... with no entity ID... somehow");
|
None
|
||||||
if this_entt == entt { None } else { Some(pos) }
|
} else {
|
||||||
|
let tsfm = boid_locations.get(entt).unwrap();
|
||||||
|
Some(tsfm.translation.xy())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.fold((0, Vec2::ZERO), |(_len, com), (idx, pos)| (idx, com + pos));
|
.fold((0, Vec2::ZERO), |(_len, com), (idx, pos)| (idx, com + pos));
|
||||||
@@ -190,7 +189,8 @@ fn cohesion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn separation(
|
fn separation(
|
||||||
spatial_tree: Res<KDTree2<TrackedByKdTree>>,
|
spatial: SpatialQuery,
|
||||||
|
boid_locations: Query<&Transform, With<Boid>>,
|
||||||
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
||||||
props: Res<FlockingParameters>,
|
props: Res<FlockingParameters>,
|
||||||
) {
|
) {
|
||||||
@@ -199,16 +199,20 @@ fn separation(
|
|||||||
// sum force from neighbors
|
// sum force from neighbors
|
||||||
// apply force
|
// apply force
|
||||||
for (this_entt, tsfm, mut force) in &mut boids {
|
for (this_entt, tsfm, mut force) in &mut boids {
|
||||||
let impulse = spatial_tree
|
let impulse = spatial
|
||||||
.within_distance(tsfm.translation.xy(), props.view_range / 4.0)
|
.shape_intersections(
|
||||||
|
&Collider::circle(props.view_range),
|
||||||
|
tsfm.translation.xy(),
|
||||||
|
0.0,
|
||||||
|
&SpatialQueryFilter::default(),
|
||||||
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(pos, entt)| {
|
.filter_map(|&entt| {
|
||||||
// Skip self-comparison. A boid should not try to separate from itself.
|
// Skip self-comparison. A boid should not try to separate from itself.
|
||||||
let entt = entt
|
|
||||||
.expect("within_distance gave me an entity... with no entity ID... somehow");
|
|
||||||
if this_entt == entt {
|
if this_entt == entt {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
let pos = boid_locations.get(entt).unwrap().translation.xy();
|
||||||
Some(pos.extend(0.0))
|
Some(pos.extend(0.0))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -223,7 +227,7 @@ fn separation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn alignment(
|
fn alignment(
|
||||||
spatial_tree: Res<KDTree2<TrackedByKdTree>>,
|
spatial: SpatialQuery,
|
||||||
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
||||||
boid_velocities: Query<&Velocity, With<Boid>>,
|
boid_velocities: Query<&Velocity, With<Boid>>,
|
||||||
props: Res<FlockingParameters>,
|
props: Res<FlockingParameters>,
|
||||||
@@ -236,14 +240,16 @@ fn alignment(
|
|||||||
// apply steering force
|
// apply steering force
|
||||||
|
|
||||||
for (this_entt, transform, mut force) in &mut boids {
|
for (this_entt, transform, mut force) in &mut boids {
|
||||||
let neighbors = spatial_tree.within_distance(transform.translation.xy(), props.view_range);
|
let neighbors = spatial.shape_intersections(
|
||||||
|
&Collider::circle(props.view_range),
|
||||||
|
transform.translation.xy(),
|
||||||
|
0.0,
|
||||||
|
&SpatialQueryFilter::default(),
|
||||||
|
);
|
||||||
// averaging divides by length. Guard against an empty set of neighbors
|
// averaging divides by length. Guard against an empty set of neighbors
|
||||||
let (len, sum) = neighbors
|
let (len, sum) = neighbors
|
||||||
.iter()
|
.iter()
|
||||||
// Extract the velocities by `get()`ing from another query param.
|
.filter_map(|&entt| {
|
||||||
.filter_map(|(_pos, maybe_entt)| {
|
|
||||||
let entt = maybe_entt
|
|
||||||
.expect("Neighbor boid has no Entity ID. I don't know what this means");
|
|
||||||
if this_entt == entt {
|
if this_entt == entt {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user