Compare commits

..

No commits in common. "394bc66207b3b8f2257b7fe21889b3ac68d2eaf0" and "2d9247448b6a031e25a961dda1d36cb1df8569c8" have entirely different histories.

4 changed files with 1606 additions and 3512 deletions

4739
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,12 @@
[package] [package]
name = "toids" name = "audubon"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2021"
[features] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
debug = ["bevy/debug"]
[dependencies] [dependencies]
argh = "0.1.12" argh = "0.1.12"
bevy = { version = "0.19", default-features = false, features = ["3d", "ui"] } bevy = "0.12.0"
async-channel = "2" bevy_spatial = "0.7.0"
rand = { version = "0.10", features = ["thread_rng"] } rand = "0.8.5"
rstar = "0.13"

View file

@ -1,21 +1,16 @@
use std::collections::{HashMap, HashSet};
use argh::FromArgs; use argh::FromArgs;
use async_channel::{bounded, Receiver, Sender}; use bevy::{
use bevy::{input::keyboard::Key, prelude::*, tasks::AsyncComputeTaskPool}; prelude::*,
use rand::RngExt; utils::{HashMap, HashSet},
use rstar::{primitives::GeomWithData, RTree}; };
use bevy_spatial::{kdtree::KDTree3, SpatialAccess};
type IndexElement = GeomWithData<[f32; 3], Entity>; pub type NNTree = KDTree3<Toid>;
type Index = RTree<IndexElement>;
type EntityPos = (Vec3, Entity);
// toid stuff // toid stuff
const SPEED: f32 = 2.0; const SPEED: f32 = 2.0;
const SPEED_DIFF_RANGE: f32 = 0.1; // +/- 10% const SPEED_DIFF_RANGE: f32 = 0.1; // +/- 10%
const MAX_DELTA_V: f32 = std::f32::consts::PI * 2.0; // basically 360 const MAX_DELTA_V: f32 = std::f32::consts::PI * 2.0; // basically 360 degrees/sec
// degrees/sec
// how far from origin before really wanting to come back // how far from origin before really wanting to come back
const RADIUS: f32 = 40.0; const RADIUS: f32 = 40.0;
@ -42,34 +37,6 @@ pub struct Buddies(HashSet<Entity>);
#[derive(Component, Debug, Clone, Deref, DerefMut, Default)] #[derive(Component, Debug, Clone, Deref, DerefMut, Default)]
pub struct Velocity(Vec3); pub struct Velocity(Vec3);
#[derive(Debug, Default, Resource)]
pub struct Naybs {
pub index: Index,
pub positions: Option<Vec<EntityPos>>,
}
#[derive(Resource)]
pub struct RawPosChannel {
tx: Sender<RawMessage>,
rx: Option<Receiver<RawMessage>>,
}
#[derive(Resource)]
pub struct IndexChannel {
tx: Sender<IndexMessage>,
rx: Receiver<IndexMessage>,
}
struct IndexMessage {
raw: Vec<EntityPos>,
index: Index,
}
struct RawMessage(Vec<EntityPos>);
#[derive(Default, Resource, Deref, DerefMut)]
pub struct BackgroundHandle(Option<bevy::tasks::Task<()>>);
#[derive(Component)] #[derive(Component)]
pub struct Toid { pub struct Toid {
pub speed: f32, pub speed: f32,
@ -79,135 +46,46 @@ pub struct Toid {
#[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)] #[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)]
pub struct LookAt(Vec3); pub struct LookAt(Vec3);
pub fn setup_channels(mut commands: Commands) {
let (raw_tx, raw_rx) = bounded(1);
commands.insert_resource(RawPosChannel {
tx: raw_tx,
rx: Some(raw_rx),
});
let (idx_tx, idx_rx) = bounded(1);
commands.insert_resource(IndexChannel {
tx: idx_tx,
rx: idx_rx,
});
}
pub fn update_naybs(
mut naybs: ResMut<Naybs>,
raw_chan: Res<RawPosChannel>,
positions: Res<Positions>,
idx_chan: Res<IndexChannel>,
) {
if let Ok(idx_msg) = idx_chan.rx.try_recv() {
naybs.index = idx_msg.index;
let _ = naybs.positions.insert(idx_msg.raw);
return;
}
if let Some(mut raw) = naybs.positions.take() {
for (i, (entity, pos)) in positions.iter().enumerate() {
raw[i] = (*pos, *entity);
}
if let Err(e) = raw_chan.tx.try_send(RawMessage(raw)) {
let RawMessage(raw) = e.into_inner();
let _ = naybs.positions.insert(raw);
}
}
}
pub fn init_naybs(positions: Res<Positions>, mut naybs: ResMut<Naybs>) {
let mut raw = Vec::with_capacity(positions.len());
for (entity, pos) in positions.iter() {
raw.push((*pos, *entity));
}
naybs.positions = Some(raw);
}
pub fn spawn_background(
mut raw_chan: ResMut<RawPosChannel>,
idx_chan: Res<IndexChannel>,
mut handle: ResMut<BackgroundHandle>,
) {
let pool = AsyncComputeTaskPool::get();
let raw_rx = raw_chan.rx.take().unwrap();
let idx_tx = idx_chan.tx.clone();
let task = pool.spawn(async move {
let tx = idx_tx;
let rx = raw_rx;
loop {
match rx.recv().await {
Ok(RawMessage(e_posns)) => {
let idx = RTree::bulk_load(
e_posns
.iter()
.map(|(p, e)| GeomWithData::new(p.to_array(), *e))
.collect(),
);
let msg = IndexMessage {
raw: e_posns,
index: idx,
};
if tx.send(msg).await.is_err() {
break;
};
}
Err(_) => break,
}
}
});
let _ = handle.0.insert(task);
}
pub fn turkey_time( pub fn turkey_time(
commands: &mut Commands, commands: &mut Commands,
scene: &Handle<Scene>,
r: &mut impl rand::prelude::Rng, r: &mut impl rand::prelude::Rng,
assets: &AssetServer, ) -> Entity {
positions: &mut HashMap<Entity, Vec3>, let speed_diff = r.gen_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
) {
let speed_diff = r.random_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
let speed = SPEED + (SPEED * speed_diff); let speed = SPEED + (SPEED * speed_diff);
let vel = unit_vec(r) * speed; let vel = unit_vec(r) * speed;
let buddies = r.random_range(6..=8); let buddies = r.gen_range(6..=8);
let x = r.random_range(-10.0..=10.0); let x = r.gen_range(-10.0..=10.0);
let z = r.random_range(-10.0..=10.0); let z = r.gen_range(-10.0..=10.0);
let y = r.random_range(MIN_ALTITUDE..=(MIN_ALTITUDE + 5.5)); let y = r.gen_range(0.1..=5.5);
let spatial_bundle = SpatialBundle {
let entity = commands transform: Transform::from_xyz(x, MIN_ALTITUDE + y, z),
.spawn(( ..Default::default()
Velocity(vel), };
Buddies::default(), commands
Toid { speed, buddies }, .spawn(spatial_bundle)
Transform::from_xyz(x, y, z), .insert((Velocity(vel), Buddies::default(), Toid { speed, buddies }))
Visibility::Visible,
))
.with_children(|t| { .with_children(|t| {
t.spawn(WorldAssetRoot( t.spawn(SceneBundle {
assets.load(GltfAssetLabel::Scene(0).from_asset("models/boid.glb")), scene: scene.to_owned(),
)) ..Default::default()
.insert(Transform::default()) })
//.insert(Visibility::Visible); .insert(Transform::default());
.insert(Transform::from_rotation(Quat::from_axis_angle( // .insert(Transform::from_rotation(Quat::from_axis_angle(
Vec3::Y, // Vec3::Y,
-std::f32::consts::FRAC_PI_2, // -std::f32::consts::FRAC_PI_2,
))); // )));
}) })
.id(); .id()
positions.insert(entity, Vec3::new(x, y, z));
} }
pub fn update_vel( pub fn update_vel(
mut toids: Query<(&Transform, &mut Velocity, &Toid, &Buddies, Entity)>, mut toids: Query<(&Transform, &mut Velocity, &Toid, &Buddies, Entity)>,
time: Res<Time>, time: Res<Time>,
positions: Res<Positions>, positions: Res<Positions>,
index: Res<Naybs>, index: Res<NNTree>,
) { ) {
let dt = time.delta_secs(); let dt = time.delta_seconds();
let max_delta = MAX_DELTA_V * dt; let max_delta = MAX_DELTA_V * dt;
for (xform, mut vel, toid, buddies, entity) in toids.iter_mut() { for (xform, mut vel, toid, buddies, entity) in toids.iter_mut() {
let speed = toid.speed; let speed = toid.speed;
@ -229,11 +107,11 @@ pub fn update_vel(
// avoid flying into neighbors // avoid flying into neighbors
let min_dist = speed; let min_dist = speed;
for neighbor in index for neighbor in index
.index .within_distance(pos, min_dist)
.locate_within_distance(pos.to_array(), min_dist) .iter()
.filter(|n| n.data != entity) .filter(|n| n.1.is_some() && n.1.unwrap() != entity)
{ {
let bp = Vec3::from_array(*neighbor.geom()); let bp = neighbor.0;
let bdir = pos - bp; let bdir = pos - bp;
let dist = bdir.length(); let dist = bdir.length();
let s = 1.0 - (dist / min_dist).min(1.0); let s = 1.0 - (dist / min_dist).min(1.0);
@ -281,7 +159,7 @@ pub fn update_pos(
time: Res<Time>, time: Res<Time>,
) { ) {
let mut new_look = Vec3::ZERO; let mut new_look = Vec3::ZERO;
let dt = time.delta_secs(); let dt = time.delta_seconds();
for (mut xform, vel, entity) in toids.iter_mut() { for (mut xform, vel, entity) in toids.iter_mut() {
let look_at = xform.translation + vel.0; let look_at = xform.translation + vel.0;
xform.translation += vel.0 * dt; xform.translation += vel.0 * dt;
@ -294,15 +172,15 @@ pub fn update_pos(
pub fn update_buddies( pub fn update_buddies(
mut toids: Query<(&Transform, Entity, &Toid, &mut Buddies)>, mut toids: Query<(&Transform, Entity, &Toid, &mut Buddies)>,
index: Res<Naybs>, index: Res<NNTree>,
positions: Res<Positions>, positions: Res<Positions>,
) { ) {
let d2 = (BUDDY_RADIUS * 1.5).powi(2); let d2 = (BUDDY_RADIUS * 1.5).powi(2);
for (xform, entity, toid, mut buddies) in toids.iter_mut() { for (xform, entity, toid, mut buddies) in toids.iter_mut() {
let pos = xform.translation; let pos = xform.translation;
for buddy in buddies.clone().iter() { for buddy in buddies.clone().iter() {
let bp = positions[buddy]; let bp = positions.get(buddy).unwrap();
let bd2 = (bp - pos).length_squared(); let bd2 = (*bp - pos).length_squared();
if bd2 > d2 { if bd2 > d2 {
buddies.remove(buddy); buddies.remove(buddy);
} }
@ -310,13 +188,12 @@ pub fn update_buddies(
if buddies.len() < toid.buddies { if buddies.len() < toid.buddies {
let diff = toid.buddies - buddies.len(); let diff = toid.buddies - buddies.len();
for neighbor in index for (_, neighbor) in index
.index .k_nearest_neighbour(pos, diff + 1)
.nearest_neighbor_iter(pos.to_array()) .into_iter()
.filter(|n| n.data != entity) .filter(|n| n.1.is_some() && n.1.unwrap() != entity)
.take(diff)
{ {
buddies.insert(neighbor.data); buddies.insert(neighbor.unwrap());
} }
} }
} }
@ -325,42 +202,41 @@ pub fn update_buddies(
pub fn update_gizmos(toids: Query<&Transform, With<Toid>>, mut gizmos: Gizmos) { pub fn update_gizmos(toids: Query<&Transform, With<Toid>>, mut gizmos: Gizmos) {
for toid in toids.iter() { for toid in toids.iter() {
let pos = toid.translation; let pos = toid.translation;
let up = toid.up().as_vec3(); let up = toid.up();
let forward = toid.forward().as_vec3(); let forward = toid.forward();
let right = toid.right().as_vec3(); let right = toid.right();
gizmos.ray(pos, pos + forward, Color::srgb(1., 0., 0.)); gizmos.ray(pos, pos + forward, Color::RED);
gizmos.ray(pos, pos + up, Color::srgb(0., 1., 0.)); gizmos.ray(pos, pos + up, Color::BLUE);
gizmos.ray(pos, pos + right, Color::srgb(0., 0., 1.)); gizmos.ray(pos, pos + right, Color::GREEN);
} }
} }
pub fn rotate_camera( pub fn rotate_camera(
mut query: Query<&mut Transform, With<Camera>>, mut query: Query<&mut Transform, With<Camera>>,
keys: Res<ButtonInput<Key>>, keys: Res<Input<KeyCode>>,
lookat: Res<LookAt>, lookat: Res<LookAt>,
) { ) {
let mut xform = query.single_mut().unwrap(); let mut xform = query.single_mut();
let forward = xform.forward().as_vec3() * 0.7; let forward = xform.forward() * 0.7;
let right = xform.right().as_vec3(); let right = xform.right();
if keys.pressed(Key::ArrowRight) { if keys.pressed(KeyCode::Right) {
xform.translation += right; xform.translation += right;
} }
if keys.pressed(Key::ArrowLeft) { if keys.pressed(KeyCode::Left) {
xform.translation -= right; xform.translation -= right;
} }
if keys.pressed(Key::ArrowUp) { if keys.pressed(KeyCode::Up) {
if keys.pressed(Key::Shift) { if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) {
xform.translation += Vec3::Y; xform.translation += Vec3::Y;
} else { } else {
xform.translation += forward; xform.translation += forward;
} }
} }
if keys.pressed(KeyCode::Down) {
if keys.pressed(Key::ArrowDown) { if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) {
if keys.pressed(Key::Shift) {
xform.translation -= Vec3::Y; xform.translation -= Vec3::Y;
} else { } else {
xform.translation -= forward; xform.translation -= forward;
@ -377,10 +253,10 @@ pub fn rotate_camera(
pub fn unit_vec(r: &mut impl rand::Rng) -> Vec3 { pub fn unit_vec(r: &mut impl rand::Rng) -> Vec3 {
let mut x1: f32 = 0.0; let mut x1: f32 = 0.0;
let mut x2: f32 = 0.0; let mut x2: f32 = 0.0;
let mut ssum = f32::MAX; let mut ssum = std::f32::MAX;
while ssum >= 1.0 { while ssum >= 1.0 {
x1 = r.random_range(-1.0..=1.0); x1 = r.gen_range(-1.0..=1.0);
x2 = r.random_range(-1.0..=1.0); x2 = r.gen_range(-1.0..=1.0);
ssum = x1.powi(2) + x2.powi(2); ssum = x1.powi(2) + x2.powi(2);
} }
let sqrt = (1.0 - ssum).sqrt(); let sqrt = (1.0 - ssum).sqrt();

View file

@ -1,46 +1,34 @@
use std::time::Duration;
use audubon::*;
use bevy::{ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
input::keyboard::Key,
prelude::*, prelude::*,
}; };
use rstar::RTree; use bevy_spatial::{AutomaticUpdate, TransformMode};
use toids::{
init_naybs, rotate_camera, setup_channels, spawn_background, turkey_time, update_buddies,
update_naybs, update_pos, update_vel, BackgroundHandle, Config, LookAt, Naybs, Positions,
};
fn main() { fn main() {
let config: toids::Config = argh::from_env(); let config: audubon::Config = argh::from_env();
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugins(( .add_plugins((
FrameTimeDiagnosticsPlugin::default(), AutomaticUpdate::<Toid>::new()
.with_transform(TransformMode::GlobalTransform)
.with_frequency(Duration::from_millis(150)),
FrameTimeDiagnosticsPlugin,
LogDiagnosticsPlugin::default(), LogDiagnosticsPlugin::default(),
)) ))
.insert_resource(config) .insert_resource(config)
.insert_resource(Positions::default()) .insert_resource(Positions::default())
.insert_resource(ClearColor(Color::srgb(0.64, 0.745, 0.937))) // a nice light blue .insert_resource(ClearColor(Color::rgb(0.64, 0.745, 0.937))) // a nice light blue
.insert_resource(GlobalAmbientLight { .insert_resource(AmbientLight {
color: Color::WHITE, color: Color::WHITE,
brightness: 150.0, brightness: 1.0,
affects_lightmapped_meshes: true,
..Default::default()
}) })
.insert_resource(Naybs {
index: RTree::new(),
positions: None,
})
.insert_resource(BackgroundHandle::default())
.insert_resource(LookAt::default()) .insert_resource(LookAt::default())
.add_systems( .add_systems(Startup, setup)
Startup,
(setup_channels, setup, init_naybs, spawn_background).chain(),
)
.add_systems(Update, (update_pos, update_buddies, update_vel).chain()) .add_systems(Update, (update_pos, update_buddies, update_vel).chain())
.add_systems( .add_systems(Update, (rotate_camera, bevy::window::close_on_esc))
Update,
(rotate_camera, close_on_esc, update_naybs.after(update_pos)),
)
//.add_systems(Update, update_gizmos) //.add_systems(Update, update_gizmos)
.run(); .run();
} }
@ -49,63 +37,42 @@ fn setup(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
mut positions: ResMut<Positions>,
config: Res<Config>, config: Res<Config>,
models: Res<AssetServer>, models: Res<AssetServer>,
) { ) {
let rand = &mut rand::rng(); let rand = &mut rand::thread_rng();
commands.spawn(( commands.spawn(Camera3dBundle {
Transform::from_xyz(0., 5.0, 25.).looking_at(Vec3::new(0.0, 5.0, 0.0), Vec3::Y), transform: Transform::from_xyz(0., 5.0, 25.).looking_at(Vec3::new(0.0, 5.0, 0.0), Vec3::Y),
Camera3d::default(), ..default()
)); });
// plane // plane
commands.spawn(( commands.spawn(PbrBundle {
Mesh3d(meshes.add(Mesh::from(Plane3d::new(Vec3::Y, Vec2::splat(500.0))))), mesh: meshes.add(Mesh::from(shape::Plane::from_size(500.0))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.9, 0.3))), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
Visibility::Visible, ..default()
Transform::default(), });
));
// let toid_model = models.load("models/boid.glb#Scene0"); let toid_model = models.load("models/boid.glb#Scene0");
for _ in 0..config.toids { for _ in 0..config.toids {
turkey_time(&mut commands, rand, &models, &mut positions.0); let _ = turkey_time(&mut commands, &toid_model, rand);
} }
// instructions // instructions
commands.spawn(( commands.spawn(
Visibility::Visible, TextBundle::from_section(
Node { "Up and down for camera forward and back; hold shift to change height.\nLeft or right to move left or right.\nPress 'ESC' to quit.",
position_type: PositionType::Absolute, TextStyle {
top: px(12.0), font_size: 20.,
left: px(12.0),
..default()
},
children![(
Text::new("Up and down for camera forward and back; hold shift to change height.\nLeft or right to move left or right.\nPress 'ESC' to quit."),
TextFont {
font_size: FontSize::Px(20.0),
..default() ..default()
}, },
)], )
)); .with_style(Style {
} position_type: PositionType::Absolute,
top: Val::Px(12.0),
fn close_on_esc( left: Val::Px(12.0),
mut commands: Commands, ..default()
focused_windows: Query<(Entity, &Window)>, }),
input: Res<ButtonInput<Key>>, );
mut handle: ResMut<BackgroundHandle>,
) {
for (window, focus) in focused_windows.iter() {
if !focus.focused {
continue;
}
if input.just_pressed(Key::Escape) {
drop((*handle).take());
commands.entity(window).despawn();
}
}
} }