bevy_boids/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#[cfg(feature = "reflect")]
use bevy::{ecs::reflect::ReflectResource, reflect::Reflect};
use bevy::{prelude::*, utils::HashMap};

pub struct BoidsPlugin;
impl Plugin for BoidsPlugin {
    fn build(&self, app: &mut App) {
        app.add_system(apply_intent.before(apply_physics))
            .add_system(apply_physics);

        #[cfg(feature = "reflect")]
        app.register_type::<BoidDescriptor>();
    }
}

#[derive(Resource)]
#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Resource))]
pub struct BoidDescriptor {
    pub thrust: f32,
    pub lift: f32,
    pub gravity: f32,
    pub bank: f32,
    pub separation: f32,
    pub alignment: f32,
    pub cohesion: f32,
    pub bank_rate: f32,
    pub rise_rate: f32,
    pub maximum_vision: f32,
}

impl Default for BoidDescriptor {
    fn default() -> Self {
        Self {
            thrust: 2.0,
            lift: 1.0,
            gravity: 1.0,
            bank: 1.0,
            separation: 70.0,
            alignment: 1.0,
            cohesion: 10.0,
            bank_rate: 0.01,
            rise_rate: 0.01,
            maximum_vision: 240.0,
        }
    }
}

#[derive(Component)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct Boid;

fn apply_intent(
    mut boids_query: Query<(Entity, &mut Transform, &GlobalTransform), With<Boid>>,
    mut headings: Local<HashMap<Entity, (f32, f32)>>,
    descriptor: Res<BoidDescriptor>,
    time: Res<Time>,
) {
    for (boid, transform, global_transform) in boids_query.iter() {
        let mut heading = Vec3::ZERO;

        for (other_boid, _, other_global_transform) in boids_query.iter() {
            if other_boid == boid {
                continue;
            }

            let position = global_transform.translation();
            let other_position = other_global_transform.translation();
            let distance = position.distance(other_position);

            if distance > descriptor.maximum_vision {
                continue;
            }

            let separation =
                (position - other_position).normalize_or_zero() * descriptor.separation / distance;
            let alignment = other_global_transform.forward() * descriptor.alignment;
            let cohesion = (other_position - position).normalize_or_zero() * descriptor.cohesion;

            heading += separation + alignment + cohesion;
        }

        let lateral =
            descriptor.bank_rate * (heading.dot(transform.left()) + transform.up().dot(Vec3::Y));
        let vertical =
            descriptor.rise_rate * (heading.dot(transform.up()) + transform.up().dot(Vec3::Y));

        headings.insert(boid, (lateral, vertical));
    }

    for (boid, mut transform, _) in boids_query.iter_mut() {
        let &(lateral, vertical) = headings.get(&boid).unwrap_or(&(0.0, 0.0));

        transform.rotate_local_z(lateral * time.delta_seconds());
        transform.rotate_local_x(vertical * time.delta_seconds());
    }
}

fn apply_physics(
    mut boids_query: Query<&mut Transform, With<Boid>>,
    descriptor: Res<BoidDescriptor>,
    time: Res<Time>,
) {
    for mut transform in boids_query.iter_mut() {
        let thrust = transform.forward() * descriptor.thrust;
        let lift = transform.up() * descriptor.lift;
        let gravity = Vec3::NEG_Y * descriptor.gravity;
        transform.translation += (thrust + lift + gravity) * time.delta_seconds();

        let bank = transform.right().dot(Vec3::Y) * descriptor.bank;
        transform.rotate_y(bank * time.delta_seconds());
    }
}