website/src/main.rs

178 lines
5.7 KiB
Rust
Raw Normal View History

2026-09-28 08:07:38 +00:00
use bevy::{platform::collections::HashMap, prelude::*};
use std::f32::consts::PI;
2026-02-20 12:46:05 +00:00
2026-09-28 19:49:33 +00:00
#[derive(Reflect, Resource)]
#[reflect(Resource)]
struct ModelConfig {
num_boids: usize,
x_bound: f32,
y_bound: f32,
max_velocity: f32,
protected_range: f32,
visible_range: f32,
alignment_factor: f32,
cohesion_factor: f32,
separation_factor: f32,
}
impl Default for ModelConfig {
fn default() -> Self {
Self {
num_boids: 50,
x_bound: 0.7,
y_bound: 0.7,
max_velocity: 200.0,
protected_range: 50.0,
visible_range: 150.0,
alignment_factor: 0.5,
cohesion_factor: 0.5,
separation_factor: 200.0,
}
}
}
2026-02-20 12:46:05 +00:00
fn main() {
App::new()
2026-09-14 16:19:58 +00:00
.insert_resource(ClearColor(Color::srgb_u8(26, 26, 46)))
2026-09-28 08:07:38 +00:00
.insert_resource(Time::<Fixed>::from_hz(60.0))
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
fit_canvas_to_parent: true,
prevent_default_event_handling: false,
2026-09-28 10:18:10 +00:00
canvas: Some("#bevy".into()),
2026-02-20 12:46:05 +00:00
..default()
}),
2026-09-28 08:07:38 +00:00
..default()
}))
2026-09-28 19:49:33 +00:00
.init_resource::<ModelConfig>()
.register_type::<ModelConfig>()
2026-02-20 12:46:05 +00:00
.add_systems(Startup, setup)
2026-09-28 08:07:38 +00:00
.add_systems(FixedUpdate, update_boids)
2026-02-20 12:46:05 +00:00
.run();
}
2026-09-28 08:07:38 +00:00
#[derive(Component)]
struct Boid {
velocity: Vec2,
2026-09-14 16:19:58 +00:00
}
2026-02-20 12:46:05 +00:00
fn setup(
2026-09-28 19:49:33 +00:00
model_config: Res<ModelConfig>,
2026-02-20 12:46:05 +00:00
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
2026-09-14 16:19:58 +00:00
mut materials: ResMut<Assets<ColorMaterial>>,
) {
2026-09-28 19:49:33 +00:00
let ModelConfig {
num_boids,
max_velocity,
..
} = *model_config;
2026-09-28 08:07:38 +00:00
2026-09-14 16:19:58 +00:00
commands.spawn(Camera2d);
2026-09-28 08:07:38 +00:00
// Triangle pointing right
let triangle_mesh = Mesh2d(meshes.add(Triangle2d::new(
Vec2::new(10.0, 0.0),
Vec2::new(-3.0, 3.0),
Vec2::new(-3.0, -3.0),
)));
let triangle_material = MeshMaterial2d(materials.add(Color::WHITE));
2026-09-28 19:49:33 +00:00
commands.spawn_batch((0..num_boids).map(move |n| {
let angle = 2.0 * PI / (num_boids as f32) * n as f32;
2026-09-28 08:07:38 +00:00
(
Boid {
2026-09-28 19:49:33 +00:00
velocity: Vec2::from_angle(angle) * max_velocity,
2026-09-28 08:07:38 +00:00
},
triangle_mesh.clone(),
triangle_material.clone(),
Transform::default().rotate(Quat::from_rotation_z(angle)),
)
}));
2026-09-14 16:19:58 +00:00
}
2026-09-28 08:07:38 +00:00
struct Model {
separation: Vec2,
alignment: Vec2,
cohesion: Vec2,
2026-09-14 16:19:58 +00:00
}
2026-09-28 08:07:38 +00:00
fn update_boids(
2026-09-28 19:49:33 +00:00
model_config: Res<ModelConfig>,
2026-09-28 08:07:38 +00:00
window: Single<&Window, With<bevy::window::PrimaryWindow>>,
time: Res<Time<Fixed>>,
2026-09-28 19:49:33 +00:00
mut query: Query<(Entity, &mut Boid, &mut Transform)>,
2026-09-14 16:19:58 +00:00
) {
2026-09-28 08:07:38 +00:00
let dt = time.delta_secs();
2026-09-28 19:49:33 +00:00
let x_bound = window.width() * model_config.x_bound;
let y_bound = window.height() * model_config.y_bound;
2026-09-14 16:19:58 +00:00
2026-09-28 08:07:38 +00:00
let mut model_updates = HashMap::new();
for (entity, boid, transform) in query.iter() {
let mut nb_neighbors: usize = 0;
let mut separation = Vec2::new(0.0, 0.0);
let mut avg_speed = Vec2::new(0.0, 0.0);
let mut avg_pos = Vec2::new(0.0, 0.0);
for (other_entity, other_boid, other_transform) in query.iter() {
if entity == other_entity {
continue;
}
2026-09-28 19:49:33 +00:00
let delta = transform.translation.xy() - other_transform.translation.xy();
let distance = delta.length();
if distance < model_config.protected_range {
if distance > f32::EPSILON {
let direction = delta / distance;
let strength = 1.0 - distance / model_config.protected_range;
separation += direction * strength;
}
} else if distance < model_config.visible_range {
2026-09-28 08:07:38 +00:00
avg_speed += other_boid.velocity;
avg_pos += other_transform.translation.xy();
nb_neighbors += 1;
}
2026-09-14 16:19:58 +00:00
}
2026-09-28 08:07:38 +00:00
if nb_neighbors > 0 {
avg_speed /= nb_neighbors as f32;
avg_pos /= nb_neighbors as f32;
2026-09-14 16:19:58 +00:00
}
2026-09-28 08:07:38 +00:00
model_updates.insert(
entity,
Model {
separation,
alignment: avg_speed - boid.velocity,
cohesion: avg_pos - transform.translation.xy(),
},
);
2026-09-14 16:19:58 +00:00
}
2026-09-28 08:07:38 +00:00
for (entity, mut boid, mut transform) in query.iter_mut() {
2026-09-28 19:49:33 +00:00
let mut out_of_bounds_acceleration = Vec2::new(0.0, 0.0);
2026-09-28 08:07:38 +00:00
if transform.translation.x < -x_bound / 2.0 {
2026-09-28 19:49:33 +00:00
out_of_bounds_acceleration.x = 1.0;
2026-09-28 08:07:38 +00:00
} else if transform.translation.x > x_bound / 2.0 {
2026-09-28 19:49:33 +00:00
out_of_bounds_acceleration.x = -1.0;
2026-09-14 16:19:58 +00:00
}
2026-09-28 08:07:38 +00:00
if transform.translation.y < -y_bound / 2.0 {
2026-09-28 19:49:33 +00:00
out_of_bounds_acceleration.y = 1.0;
2026-09-28 08:07:38 +00:00
} else if transform.translation.y > y_bound / 2.0 {
2026-09-28 19:49:33 +00:00
out_of_bounds_acceleration.y = -1.0;
2026-09-28 08:07:38 +00:00
}
2026-09-28 19:49:33 +00:00
let out_of_bounds_acceleration = out_of_bounds_acceleration.normalize_or_zero();
2026-09-28 08:07:38 +00:00
let model_update = model_updates.get(&entity).unwrap();
2026-09-28 19:49:33 +00:00
let boundary_force = out_of_bounds_acceleration * model_config.max_velocity;
let separation_force = model_update.separation * model_config.separation_factor;
let alignment_force = model_update.alignment * model_config.alignment_factor;
let cohesion_force = model_update.cohesion * model_config.cohesion_factor;
let acceleration = boundary_force + separation_force + alignment_force + cohesion_force;
boid.velocity += acceleration * dt;
boid.velocity = boid.velocity.clamp_length_max(model_config.max_velocity);
2026-09-28 08:07:38 +00:00
transform.translation.x += boid.velocity.x * dt;
transform.translation.y += boid.velocity.y * dt;
transform.rotation = Quat::from_rotation_z(boid.velocity.y.atan2(boid.velocity.x));
2026-09-14 16:19:58 +00:00
}
2026-02-20 12:46:05 +00:00
}