initial commit
This commit is contained in:
commit
be5e225a1e
3506
Cargo.lock
generated
Normal file
3506
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "boids"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.10.1", features = [ "trace" ] }
|
||||
rand = "0.8.5"
|
BIN
assets/bg.png
Normal file
BIN
assets/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 622 KiB |
BIN
assets/boid.png
Normal file
BIN
assets/boid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
assets/fonts/DejaVuSans.ttf
Normal file
BIN
assets/fonts/DejaVuSans.ttf
Normal file
Binary file not shown.
185
src/boids.rs
Normal file
185
src/boids.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use bevy::prelude::*;
|
||||
use crate::map::Map;
|
||||
use crate::Params;
|
||||
|
||||
pub const NBR : usize = 10_000;
|
||||
pub const SIZE : f32 = 100.0;
|
||||
pub const VIEW_DIST : f32 = 1.0;
|
||||
pub const BORDER : f32 = 1.0;
|
||||
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BoidBundle {
|
||||
mesh: SpriteBundle,
|
||||
pos: Pos,
|
||||
vel: Vel,
|
||||
dir_vec: DirVec,
|
||||
prox_cache: ProxCache
|
||||
}
|
||||
|
||||
impl BoidBundle {
|
||||
pub fn new(
|
||||
pos: Vec2, vel: Vec2,
|
||||
mesh: SpriteBundle
|
||||
) -> Self {
|
||||
BoidBundle {
|
||||
mesh,
|
||||
pos: Pos(pos),
|
||||
vel: Vel(vel),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoidPlugin;
|
||||
|
||||
impl Plugin for BoidPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(Map::new(SIZE as usize));
|
||||
app.add_systems((
|
||||
boids_proxcache,
|
||||
boids_sep,
|
||||
boids_ali,
|
||||
boids_coh,
|
||||
boids_dir,
|
||||
boids_move,
|
||||
mesh_update
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Debug, Clone, Copy)]
|
||||
pub struct Pos(pub Vec2);
|
||||
|
||||
#[derive(Component, Default, Debug, Clone, Copy)]
|
||||
pub struct Vel(pub Vec2);
|
||||
|
||||
#[derive(Component, Default, Debug)]
|
||||
pub struct DirVec(Vec<Vec2>);
|
||||
|
||||
#[derive(Component, Default, Debug)]
|
||||
pub struct ProxCache(Vec<(Entity,Vec2,f32)>);
|
||||
|
||||
fn boids_proxcache(
|
||||
map: Res<Map>,
|
||||
mut query0: Query<(Entity, &Pos, &mut ProxCache)>,
|
||||
query1: Query<&Pos>
|
||||
) {
|
||||
query0.par_iter_mut()
|
||||
.for_each_mut(|(e0, Pos(p0), mut prox_cache)| {
|
||||
prox_cache.0.clear();
|
||||
for e1 in map.get(p0, VIEW_DIST) {
|
||||
if e0 == e1 { continue }
|
||||
let Ok(Pos(p1)) = query1.get(e1) else {
|
||||
error!("entity not found! should never occure!");
|
||||
continue;
|
||||
};
|
||||
let diff = *p0 - *p1;
|
||||
let d = diff.length_squared();
|
||||
if d > VIEW_DIST*VIEW_DIST || d == 0.0 { continue }
|
||||
prox_cache.0.push((e1, diff, d));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn boids_sep(
|
||||
params: Res<Params>,
|
||||
mut query: Query<(&Pos, &ProxCache, &mut DirVec)>,
|
||||
) {
|
||||
for (Pos(p0), ProxCache(prox_cache), mut dir_vec) in &mut query {
|
||||
let mut sep = Vec2::default();
|
||||
|
||||
for (_,diff,d) in prox_cache {
|
||||
sep += *diff / *d;
|
||||
}
|
||||
dir_vec.0.push(sep * params.sep);
|
||||
dir_vec.0.push(border_vel(p0) * 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
fn boids_coh(
|
||||
params: Res<Params>,
|
||||
mut query: Query<(&ProxCache, &mut DirVec)>,
|
||||
) {
|
||||
for (ProxCache(prox_cache), mut dir_vec) in &mut query {
|
||||
let mut coh = Vec2::default();
|
||||
|
||||
for (_,diff,_) in prox_cache {
|
||||
coh -= *diff;
|
||||
}
|
||||
dir_vec.0.push(coh * params.coh);
|
||||
}
|
||||
}
|
||||
|
||||
fn boids_ali(
|
||||
params: Res<Params>,
|
||||
mut query: Query<(&ProxCache, &mut DirVec)>,
|
||||
query2: Query<&Vel>
|
||||
) {
|
||||
for (ProxCache(prox_cache), mut dir_vec) in &mut query {
|
||||
let mut ali = Vec2::default();
|
||||
|
||||
for (e1,_,_) in prox_cache {
|
||||
let Ok(Vel(v1)) = query2.get(*e1) else {
|
||||
error!("entity not found! should never occure!");
|
||||
continue;
|
||||
};
|
||||
ali += *v1;
|
||||
}
|
||||
dir_vec.0.push(ali * params.ali);
|
||||
}
|
||||
}
|
||||
|
||||
fn border_vel(p0: &Vec2) -> Vec2 {
|
||||
Vec2::new(
|
||||
match p0.x {
|
||||
_ if p0.x < -SIZE+BORDER => 1.0,
|
||||
_ if p0.x > SIZE-BORDER => -1.0,
|
||||
_ => 0.0
|
||||
},
|
||||
match p0.y {
|
||||
_ if p0.y < -SIZE+BORDER => 1.0,
|
||||
_ if p0.y > SIZE-BORDER => -1.0,
|
||||
_ => 0.0
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn boids_dir(mut query: Query<(&mut DirVec, &mut Vel)>) {
|
||||
for (mut dir_vec, mut vel) in &mut query {
|
||||
for v in &dir_vec.0 {
|
||||
vel.0 += *v;
|
||||
}
|
||||
vel.0 = vel.0.normalize();
|
||||
dir_vec.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn boids_move(
|
||||
time: Res<Time>,
|
||||
params: Res<Params>,
|
||||
mut map: ResMut<Map>,
|
||||
mut query: Query<(Entity, &mut Pos, &Vel)>
|
||||
) {
|
||||
let dt = time.delta_seconds();
|
||||
for (e, mut pos, vel) in &mut query {
|
||||
let new_pos = pos.0 + vel.0 * dt * params.speed;
|
||||
let new_pos = Vec2::new(
|
||||
new_pos.x.clamp(-SIZE, SIZE),
|
||||
new_pos.y.clamp(-SIZE, SIZE)
|
||||
);
|
||||
if pos.0 != new_pos {
|
||||
map.update(&pos.0, &new_pos, &e);
|
||||
}
|
||||
pos.0 = new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
fn mesh_update(mut query: Query<(&mut Transform, &Pos, &Vel)>) {
|
||||
for (mut transform, Pos(pos), Vel(vel)) in &mut query {
|
||||
let pos3 = Vec3::new(pos.x, pos.y, 0.0);
|
||||
let angle = Vec2::X.angle_between(*vel);
|
||||
|
||||
*transform = Transform::from_translation(pos3)
|
||||
.with_rotation(Quat::from_rotation_z(angle));
|
||||
}
|
||||
}
|
59
src/camera.rs
Normal file
59
src/camera.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::input::mouse::{MouseWheel,MouseMotion};
|
||||
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct CamBundle {
|
||||
cam: Camera2dBundle,
|
||||
state: CameraState
|
||||
}
|
||||
|
||||
impl CamBundle {
|
||||
pub fn new(scale: f32) -> Self {
|
||||
CamBundle {
|
||||
cam: Camera2dBundle {
|
||||
transform: Transform::from_scale(Vec3::new(scale, scale, 1.0)),
|
||||
..default()
|
||||
},
|
||||
state: CameraState { scale, ..default() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct CameraState {
|
||||
scale: f32,
|
||||
pos: Vec2
|
||||
}
|
||||
|
||||
pub struct CameraPlugin;
|
||||
|
||||
impl Plugin for CameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_system(mouse_move_events);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_move_events(
|
||||
buttons: Res<Input<MouseButton>>,
|
||||
mut mouse_wheel_events: EventReader<MouseWheel>,
|
||||
mut mouse_motion_events: EventReader<MouseMotion>,
|
||||
mut query: Query<(&mut Transform, &mut CameraState)>
|
||||
) {
|
||||
let (mut transform, mut state) = query.single_mut();
|
||||
|
||||
for event in mouse_wheel_events.iter() {
|
||||
let scale = state.scale;
|
||||
state.scale = (scale + event.y * 0.25 * scale)
|
||||
.clamp(0.001, 1.0);
|
||||
*transform = transform.with_scale(Vec3::new(state.scale,
|
||||
state.scale, 1.0));
|
||||
}
|
||||
if buttons.pressed(MouseButton::Left) {
|
||||
let scale = state.scale;
|
||||
for event in mouse_motion_events.iter() {
|
||||
state.pos -= event.delta * scale;
|
||||
}
|
||||
transform.translation = Vec3::new(state.pos.x, -state.pos.y, 0.0);
|
||||
}
|
||||
}
|
||||
|
148
src/main.rs
Normal file
148
src/main.rs
Normal file
@ -0,0 +1,148 @@
|
||||
mod map;
|
||||
mod boids;
|
||||
mod camera;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::diagnostic::{ Diagnostics, FrameTimeDiagnosticsPlugin };
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::time::Duration;
|
||||
|
||||
use boids::{NBR,BORDER,SIZE,BoidPlugin,BoidBundle};
|
||||
use map::Map;
|
||||
use camera::{CameraPlugin,CamBundle};
|
||||
|
||||
#[derive(Resource,Debug)]
|
||||
pub struct Params {
|
||||
sep: f32,
|
||||
ali: f32,
|
||||
coh: f32,
|
||||
speed: f32
|
||||
}
|
||||
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
Params {
|
||||
sep: 0.02,
|
||||
ali: 0.2,
|
||||
coh: 0.02,
|
||||
speed: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct StatsText;
|
||||
|
||||
#[derive(Resource)]
|
||||
struct FpsTimer(Timer);
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Bevy :: Boids".into(),
|
||||
resolution: (900., 900.).into(),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.init_resource::<Params>()
|
||||
.add_plugin(BoidPlugin)
|
||||
.add_plugin(CameraPlugin)
|
||||
.insert_resource(FpsTimer(Timer::new(Duration::from_secs_f32(0.5),
|
||||
TimerMode::Repeating)))
|
||||
.add_system(update_stats)
|
||||
.add_startup_system(setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut map: ResMut<Map>,
|
||||
asset_server: Res<AssetServer>
|
||||
) {
|
||||
// Camera
|
||||
commands.spawn(CamBundle::new(0.1));
|
||||
|
||||
//BG
|
||||
commands.spawn(SpriteBundle {
|
||||
texture: asset_server.load("bg.png"),
|
||||
transform: Transform::from_scale(Vec3::new(SIZE as f32 / 512.0,
|
||||
SIZE as f32 / 512.0, 1.0))
|
||||
.with_translation(Vec3::new(0.0, 0.0, -1.0)),
|
||||
..default()
|
||||
});
|
||||
|
||||
let tex = asset_server.load("boid.png");
|
||||
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..NBR {
|
||||
let pos = Vec2::new(rng.gen_range(-SIZE+BORDER..SIZE-BORDER),
|
||||
rng.gen_range(-SIZE+BORDER..SIZE-BORDER));
|
||||
let v = Vec2::new(rng.gen_range(-1.0..1.0),
|
||||
rng.gen_range(-1.0..1.0)).normalize();
|
||||
let sprite = SpriteBundle {
|
||||
sprite: Sprite {
|
||||
custom_size: Some(Vec2::new(2.0, 2.0)),
|
||||
..default()
|
||||
},
|
||||
texture: tex.clone(),
|
||||
..default()
|
||||
};
|
||||
|
||||
let entity = commands.spawn(BoidBundle::new(pos, v, sprite)).id();
|
||||
map.insert(&pos, &entity);
|
||||
}
|
||||
|
||||
// Unashamely stolen from the website examples...
|
||||
let text_section = move |color, value: &str| {
|
||||
TextSection::new(
|
||||
value,
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/DejaVuSans.ttf"),
|
||||
font_size: 20.0,
|
||||
color,
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
TextBundle::from_sections([
|
||||
text_section(Color::GREEN, "Boid Count: "),
|
||||
text_section(Color::CYAN, &format!("{}", NBR)),
|
||||
text_section(Color::GREEN, "\nFPS: "),
|
||||
text_section(Color::CYAN, ""),
|
||||
])
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
top: Val::Px(5.0),
|
||||
left: Val::Px(5.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
}),
|
||||
StatsText,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
fn update_stats(
|
||||
diagnostics: Res<Diagnostics>,
|
||||
mut query: Query<&mut Text, With<StatsText>>,
|
||||
mut fps_timer: ResMut<FpsTimer>,
|
||||
time: Res<Time>
|
||||
) {
|
||||
fps_timer.0.tick(time.delta());
|
||||
if !fps_timer.0.finished() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut text = query.single_mut();
|
||||
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) {
|
||||
if let Some(ema) = fps.smoothed() {
|
||||
text.sections[3].value = format!("{ema:.2}");
|
||||
}
|
||||
}
|
||||
}
|
88
src/map.rs
Normal file
88
src/map.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Resource,Default,Debug)]
|
||||
pub struct Map { size: usize,
|
||||
map: Vec<Vec<HashSet<Entity>>>
|
||||
}
|
||||
|
||||
impl Map {
|
||||
pub fn new(size: usize) -> Self {
|
||||
Map { size,
|
||||
map: vec! [ vec![ HashSet::default() ; size*2 +1 ] ; size*2 + 1]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: &Vec2, entity: &Entity) {
|
||||
let (x, y) = self.get_key(pos);
|
||||
self.map[y][x].insert(*entity);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, old_pos: &Vec2, new_pos: &Vec2, entity: &Entity) {
|
||||
let (old_x, old_y) = self.get_key(old_pos);
|
||||
let (new_x, new_y) = self.get_key(new_pos);
|
||||
self.map[old_y][old_x].remove(entity);
|
||||
self.map[new_y][new_x].insert(*entity);
|
||||
}
|
||||
|
||||
pub fn get(&self, pos: &Vec2, view_dist: f32) -> Vec<Entity> {
|
||||
let vd = Vec2::new(view_dist, view_dist);
|
||||
let (from_x, from_y) = self.get_key(&(*pos - vd));
|
||||
let (to_x, to_y) = self.get_key(&(*pos + vd));
|
||||
let mut v = vec![];
|
||||
|
||||
for y in from_y..=to_y {
|
||||
for x in from_x..=to_x {
|
||||
v.extend(self.map[y][x].iter());
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
fn get_key(&self, pos: &Vec2) -> (usize, usize) {
|
||||
let size = self.size as i32;
|
||||
((pos.x as i32 + size)
|
||||
.clamp(0, size*2) as usize,
|
||||
(pos.y as i32 + size)
|
||||
.clamp(0, size*2) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
use std::collections::{HashMap,HashSet};
|
||||
|
||||
#[derive(Resource,Default,Debug)]
|
||||
pub struct Map(HashMap<(i32,i32), HashSet<Entity>>);
|
||||
|
||||
impl Map {
|
||||
pub fn update(&mut self, old_pos: &Vec2, new_pos: &Vec2, entity: &Entity) {
|
||||
let old_p = (old_pos.x as i32, old_pos.y as i32);
|
||||
let new_p = (new_pos.x as i32, new_pos.y as i32);
|
||||
|
||||
self.0.entry(old_p)
|
||||
.and_modify(| set | { set.remove(entity); });
|
||||
self.0.entry(new_p)
|
||||
.and_modify(| set | { set.insert(*entity); })
|
||||
.or_insert([*entity].into());
|
||||
}
|
||||
|
||||
pub fn get(&self, pos: &Vec2, view_dist: f32) -> Vec<Entity> {
|
||||
let from_x = (pos.x - view_dist) as i32;
|
||||
let from_y = (pos.y - view_dist) as i32;
|
||||
let to_x = (pos.x + view_dist) as i32;
|
||||
let to_y = (pos.y + view_dist) as i32;
|
||||
let mut v = vec![];
|
||||
|
||||
for y in from_y..=to_y {
|
||||
for x in from_x..=to_x {
|
||||
if let Some(set) = self.0.get(&(x,y)) {
|
||||
v.extend(set.iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
*/
|
52
src/mouse_inputs.rs
Normal file
52
src/mouse_inputs.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use bevy::prelude::*;
|
||||
//use bevy::input::mouse::{MouseButtonInput,MouseWheel,MouseMotion};
|
||||
use bevy::input::mouse::{MouseWheel,MouseMotion};
|
||||
//use bevy::input::ButtonState;
|
||||
|
||||
use crate::CameraState;
|
||||
|
||||
pub fn mouse_move_events(
|
||||
buttons: Res<Input<MouseButton>>,
|
||||
mut mouse_wheel_events: EventReader<MouseWheel>,
|
||||
mut mouse_motion_events: EventReader<MouseMotion>,
|
||||
mut query: Query<(&mut Transform, &mut CameraState)>
|
||||
) {
|
||||
let (mut transform, mut state) = query.single_mut();
|
||||
|
||||
for event in mouse_wheel_events.iter() {
|
||||
let scale = state.scale;
|
||||
state.scale = (scale + event.y * scale)
|
||||
.clamp(0.001, 0.1);
|
||||
*transform = transform.with_scale(Vec3::new(state.scale,
|
||||
state.scale, 1.0));
|
||||
}
|
||||
if buttons.pressed(MouseButton::Left) {
|
||||
let scale = state.scale;
|
||||
for event in mouse_motion_events.iter() {
|
||||
state.pos -= event.delta * scale;
|
||||
}
|
||||
transform.translation = Vec3::new(state.pos.x, -state.pos.y, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn mouse_button_events(
|
||||
mut commands: Commands,
|
||||
mut mouse_button_events: EventReader<MouseButtonInput>
|
||||
) {
|
||||
for _event in mouse_button_events.iter() {
|
||||
|
||||
if event.button == MouseButton::Left &&
|
||||
event.state == ButtonState::Pressed {
|
||||
} else if event.button == MouseButton::Right &&
|
||||
event.state == ButtonState::Pressed {
|
||||
for (entity,_) in &query {
|
||||
commands.entity(entity).despawn();
|
||||
terrain_events
|
||||
.send(GenTerrainEvent { seed: rand::random() });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
Loading…
Reference in New Issue
Block a user