Compare commits

..

No commits in common. "5158fba63bccf050103362f029e22d75988d0208" and "ed7b947f1268a6603c5a25225009c156906b2842" have entirely different histories.

8 changed files with 1258 additions and 2364 deletions

View file

@ -1,4 +0,0 @@
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
wrap_comments = true
edition = "2021"

3200
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
argh = "0.1" argh = "0.1.12"
bevy = "0.15.0-rc.3" bevy = "0.12.0"
bevy_spatial = { git = "https://github.com/laundmo/bevy-spatial.git", rev = "9ac313ac6173b440f50954b696dc2063513b4f37" } #"0.10.0" rand = "0.8.5"
rand = "0.8" rstar = "0.11.0"

Binary file not shown.

Binary file not shown.

View file

@ -1,39 +1,31 @@
use argh::FromArgs; use argh::FromArgs;
use bevy::{ use bevy::prelude::*;
prelude::*,
tasks::ParallelIterator,
utils::{HashMap, HashSet},
};
use bevy_spatial::{kdtree::KDTree3, SpatialAccess};
pub type NNTree = KDTree3<Toid>; // toid
const SPEED: f32 = 1.0;
// toid stuff const SPEED_DIFF_RANGE: f32 = 0.08; // +/- 8%
const SPEED: f32 = 15.0;
const SPEED_DIFF_RANGE: f32 = 0.1; // +/- 10%
const MAX_DELTA_V: f32 = std::f32::consts::PI * 2.0; // basically 360 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 = 50.0; const RADIUS: f32 = 100.0;
// how close to try to stay to your buddies const MIN_ALTITUDE: f32 = 1.5;
const BUDDY_RADIUS: f32 = SPEED;
const MIN_ALTITUDE: f32 = 13.5; pub type Point = [f32; 3];
pub type Toint = rstar::primitives::GeomWithData<Point, Entity>;
#[derive(Debug, FromArgs, Resource)] #[derive(Debug, FromArgs, Resource)]
/// Toid Watching /// Toid Watching
pub struct Config { pub struct Config {
/// how many Toids to spawn /// how many Toids to spawn
#[argh(option, short = 't', default = "5_000")] #[argh(option, short = 't', default = "100")]
pub toids: usize, pub toids: usize,
} }
#[derive(Clone, Debug, Default, Deref, DerefMut, Resource)] #[derive(Resource, Deref, DerefMut, Default)]
pub struct Positions(pub HashMap<Entity, Vec3>); pub struct Index(pub rstar::RTree<Toint>);
#[derive(Component, Debug, Deref, DerefMut, Clone, Default)] #[derive(Component, Debug, Clone, Deref, DerefMut, Default)]
pub struct Buddies(HashSet<Entity>); pub struct Buddies(Vec<Toint>);
#[derive(Component, Debug, Clone, Deref, DerefMut, Default)] #[derive(Component, Debug, Clone, Deref, DerefMut, Default)]
pub struct Velocity(Vec3); pub struct Velocity(Vec3);
@ -44,12 +36,6 @@ pub struct Toid {
pub buddies: usize, pub buddies: usize,
} }
#[derive(Resource, Debug, Deref, DerefMut, Clone, Default)]
pub struct Paused(bool);
#[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)]
pub struct LookAt(Vec3);
pub fn turkey_time( pub fn turkey_time(
commands: &mut Commands, commands: &mut Commands,
scene: &Handle<Scene>, scene: &Handle<Scene>,
@ -59,207 +45,79 @@ pub fn turkey_time(
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.gen_range(6..=8); let buddies = r.gen_range(6..=8);
let x = r.gen_range(-10.0..=10.0);
let z = r.gen_range(-10.0..=10.0);
let y = r.gen_range(0.1..=5.5);
let pos = Vec3::new(x, MIN_ALTITUDE + y, z);
let xform = Transform::from_translation(pos);
let spatial_bundle = (xform, Visibility::Visible);
commands commands
.spawn(spatial_bundle) .spawn(SpatialBundle::default())
.insert((Velocity(vel), Buddies::default(), Toid { speed, buddies })) .insert((Velocity(vel), Buddies::default(), Toid { speed, buddies }))
.with_children(|t| { .with_children(|t| {
t.spawn(SceneRoot(scene.to_owned())) t.spawn(SceneBundle {
.insert(Transform::default()); scene: scene.to_owned(),
..Default::default()
})
.insert(Transform::from_rotation(Quat::from_axis_angle(
Vec3::Y,
-std::f32::consts::FRAC_PI_2,
)));
}) })
.id() .id()
} }
pub fn update_vel( pub fn add_gizmos(mut gizmos: Gizmos, toids: Query<(&Transform, Entity), With<Toid>>) {
mut toids: Query<(&Transform, &mut Velocity, &Toid, &Buddies, Entity)>, let gizmos = &mut gizmos;
time: Res<Time>, for (pos, _entity) in toids.iter() {
positions: Res<Positions>, let nudge = pos.up() * 0.15;
index: Res<NNTree>, let rpos = pos.translation + nudge;
paused: Res<Paused>, gizmos.ray(rpos, pos.forward(), Color::RED);
) {
if **paused {
return;
} }
let dt = time.delta_secs();
let max_delta = MAX_DELTA_V * dt;
toids
.par_iter_mut()
.for_each(|(xform, mut vel, toid, buddies, entity)| {
let speed = toid.speed;
let mut dir = vel.normalize();
let pos = xform.translation;
let original_dir = dir;
// find buddies and orient; point more towards further-away buddies
for buddy in buddies.iter() {
let bp = positions.get(buddy).unwrap();
let bdir = *bp - pos;
let dist = bdir.length();
let rot = Quat::from_rotation_arc(dir, bdir.normalize());
let s = (dist / (BUDDY_RADIUS * 1.2)).min(1.0);
let rot = Quat::IDENTITY.slerp(rot, s);
dir = rot.mul_vec3(dir).normalize();
}
// avoid flying into neighbors
let min_dist = speed * 20.0;
for neighbor in index
.within_distance(pos, min_dist)
.iter()
.filter(|n| n.1.is_some() && n.1.unwrap() != entity)
{
let bp = neighbor.0;
let bdir = pos - bp;
let dist = bdir.length();
let s = 1.0 - (dist / min_dist).min(1.0);
let rot = Quat::from_rotation_arc(dir, bdir.normalize());
let rot = Quat::IDENTITY.slerp(rot, s);
dir = rot.mul_vec3(dir).normalize();
}
// nudge toward origin if too far
{
let dist = pos.length();
let toward_origin = -pos.normalize();
let s = (dist / RADIUS).min(1.0);
let rot = Quat::from_rotation_arc(dir, toward_origin);
let rot = Quat::IDENTITY.slerp(rot, s);
dir = rot.mul_vec3(dir).normalize();
}
// nudge up if too low
if pos.y < MIN_ALTITUDE {
let dh = MIN_ALTITUDE - pos.y;
let s = (dh / MIN_ALTITUDE).min(1.0);
let rot = Quat::from_rotation_arc(dir, Vec3::Y);
let rot = Quat::IDENTITY.slerp(rot, s);
dir = rot.mul_vec3(dir).normalize();
}
// make sure velocity doesn't change too suddenly
let delta = dir.dot(original_dir).acos();
if delta > max_delta {
let s = max_delta / delta;
let rot = Quat::from_rotation_arc(original_dir, dir);
let rot = Quat::IDENTITY.slerp(rot, s);
dir = rot.mul_vec3(original_dir).normalize();
}
**vel = dir * speed;
});
} }
pub fn update_pos( pub fn update_pos(
mut toids: Query<(&mut Transform, &Velocity, Entity), With<Toid>>, mut toids: Query<(&mut Transform, &Velocity, Entity)>,
mut positions: ResMut<Positions>,
mut lookat: ResMut<LookAt>,
time: Res<Time>, time: Res<Time>,
paused: Res<Paused>, mut index: ResMut<Index>,
) { ) {
if **paused { let mut positions = Vec::with_capacity(toids.iter().len());
return;
}
let mut new_look = Vec3::ZERO; let dt = time.delta_seconds();
let dt = time.delta_secs();
for (mut xform, vel, entity) in toids.iter_mut() { for (mut xform, vel, entity) in toids.iter_mut() {
xform.translation += **vel * dt; let look_at = xform.translation + vel.0;
let look_at = vel.normalize(); xform.translation += vel.0 * dt;
xform.look_to(look_at, Vec3::Y); xform.look_at(look_at, Vec3::Y);
*positions.entry(entity).or_insert(Vec3::ZERO) = xform.translation; let toint = Toint::new(xform.translation.to_array(), entity);
new_look += xform.translation; positions.push(toint);
} }
**lookat = new_look / positions.len() as f32; let tree = rstar::RTree::bulk_load(positions);
**index = tree;
} }
pub fn update_buddies( pub fn update_vel(
mut toids: Query<(&Transform, Entity, &Toid, &mut Buddies)>, mut toids: Query<(&Transform, &mut Velocity, &Toid), With<Toid>>,
index: Res<NNTree>, time: Res<Time>,
positions: Res<Positions>,
) { ) {
let d2 = (BUDDY_RADIUS * 1.5).powi(2); let dt = time.delta_seconds();
for (xform, entity, toid, mut buddies) in toids.iter_mut() { for (xform, mut vel, toid) in toids.iter_mut() {
let speed = toid.speed;
let mut dir = xform.forward();
let pos = xform.translation; let pos = xform.translation;
for buddy in buddies.clone().iter() {
let bp = positions.get(buddy).unwrap(); // nudge up if too low
let bd2 = (*bp - pos).length_squared(); if pos.y < MIN_ALTITUDE {
if bd2 > d2 { let dh = MIN_ALTITUDE - pos.y;
buddies.remove(buddy); let pct = (dh / MIN_ALTITUDE).min(1.0);
} let up = Quat::from_rotation_arc(dir, Vec3::Y);
let rot = xform.rotation.slerp(up, pct);
dir = rot.mul_vec3(dir);
} }
if buddies.len() < toid.buddies { // nudge toward origin if too far
let diff = toid.buddies - buddies.len(); let dist = pos.length();
for (_, neighbor) in index
.k_nearest_neighbour(pos, diff + 1) // find buddies and orient: point more towards further-away buddies
.into_iter()
.filter(|n| n.1.is_some() && n.1.unwrap() != entity) **vel = dir * speed;
{
buddies.insert(neighbor.unwrap());
}
}
} }
} }
pub fn update_gizmos(toids: Query<&Transform, With<Toid>>, mut gizmos: Gizmos) { pub fn update_buddies(mut toids: Query<(&Transform, Entity, &mut Buddies)>, index: Res<Index>) {}
for toid in toids.iter() {
let pos = toid.translation;
//dbg!(toid);
let up = toid.up().as_vec3();
let forward = toid.forward().as_vec3();
let right = toid.right().as_vec3();
gizmos.ray(pos, pos + forward, Color::srgb_u8(255, 0, 0));
gizmos.ray(pos, pos + up, Color::srgb_u8(0, 0, 255));
gizmos.ray(pos, pos + right, Color::srgb_u8(0, 255, 0));
}
}
pub fn rotate_camera(
mut query: Query<&mut Transform, With<Camera>>,
mut paused: ResMut<Paused>,
keys: Res<ButtonInput<KeyCode>>,
lookat: Res<LookAt>,
) {
let mut xform = query.single_mut();
let forward = xform.forward() * 0.7;
let right = xform.right().as_vec3();
if keys.just_pressed(KeyCode::Space) {
let pause = **paused;
**paused = !pause;
}
if keys.pressed(KeyCode::ArrowRight) {
xform.translation += right;
}
if keys.pressed(KeyCode::ArrowLeft) {
xform.translation -= right;
}
if keys.pressed(KeyCode::ArrowUp) {
if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) {
xform.translation += Vec3::Y;
} else {
xform.translation += forward;
}
}
if keys.pressed(KeyCode::ArrowDown) {
if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) {
xform.translation -= Vec3::Y;
} else {
xform.translation -= forward;
}
}
xform.look_at(**lookat, Vec3::Y);
}
//-************************************************************************ //-************************************************************************
// util // util
@ -268,7 +126,7 @@ 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.gen_range(-1.0..=1.0); x1 = r.gen_range(-1.0..=1.0);
x2 = r.gen_range(-1.0..=1.0); x2 = r.gen_range(-1.0..=1.0);

View file

@ -1,36 +1,29 @@
use std::time::Duration; //use std::f32::consts::PI;
use bevy::prelude::*;
use audubon::*; use audubon::*;
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
};
use bevy_spatial::{AutomaticUpdate, TransformMode};
fn main() { fn main() {
let config: audubon::Config = argh::from_env(); let config: audubon::Config = argh::from_env();
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_plugins(( .insert_resource(Index(rstar::RTree::new()))
AutomaticUpdate::<Toid>::new()
.with_transform(TransformMode::GlobalTransform)
.with_frequency(Duration::from_millis(150)),
FrameTimeDiagnosticsPlugin,
LogDiagnosticsPlugin::default(),
))
.insert_resource(config) .insert_resource(config)
.insert_resource(Positions::default()) .add_systems(Startup, setup)
.insert_resource(ClearColor(Color::srgb(0.64, 0.745, 0.937))) // a nice light blue .add_systems(
.insert_resource(AmbientLight { Update,
color: Color::WHITE, (
brightness: 1.0, update_vel,
}) add_gizmos,
.insert_resource(LookAt::default()) update_pos,
.insert_resource(Paused::default()) update_buddies,
.add_systems(Startup, (setup, setup_ambient_light)) rotate_camera,
//.add_systems(Update, update_gizmos) update_config,
.add_systems(Update, (update_pos, update_buddies, update_vel)) bevy::window::close_on_esc,
.add_systems(Update, (rotate_camera, close_on_esc)) )
.chain(),
)
.run(); .run();
} }
@ -42,61 +35,76 @@ fn setup(
models: Res<AssetServer>, models: Res<AssetServer>,
) { ) {
let rand = &mut rand::thread_rng(); let rand = &mut rand::thread_rng();
let cam = Camera3d::default();
let camera = commands commands.spawn(Camera3dBundle {
.spawn(( transform: Transform::from_xyz(0., 3.5, 10.).looking_at(Vec3::ZERO, Vec3::Y),
Transform::from_xyz(0., 5.0, 25.).looking_at(Vec3::new(0.0, 5.0, 0.0), Vec3::Y), ..default()
cam, });
Visibility::Hidden,
))
.id();
// plane // plane
commands commands.spawn(PbrBundle {
.spawn(Mesh3d( mesh: meshes.add(Mesh::from(shape::Plane::from_size(50.0))),
meshes.add(Mesh::from(Plane3d::default().mesh().size(500.0, 500.0))), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
)) ..default()
.insert(MeshMaterial3d(materials.add(Color::srgb(0.3, 1.0, 0.3)))); });
let toid_model = models.load("models/toid3.glb#Scene0"); let toid_model = models.load("models/boid.glb#Scene0");
for _ in 0..config.toids { for _ in 0..config.toids {
let _ = turkey_time(&mut commands, &toid_model, rand); let _ = turkey_time(&mut commands, &toid_model, rand);
} }
// instructions // light
commands.spawn(( commands.spawn(PointLightBundle {
Text::new( point_light: PointLight {
"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 { intensity: 1500.0,
font_size: 20.0, shadows_enabled: true,
..Default::default() ..default()
}, },
Node { transform: Transform::from_xyz(4.0, 8.0, 4.0),
position_type: PositionType::Absolute, ..default()
top: Val::Px(12.0), });
left: Val::Px(12.0),
// example instructions
commands.spawn(
TextBundle::from_section(
"Press 'D' to toggle drawing gizmos on top of everything else in the scene\n\
Press 'P' to toggle perspective for line gizmos\n\
Hold 'Left' or 'Right' to change the line width",
TextStyle {
font_size: 20.,
..default() ..default()
}, },
TargetCamera(camera), )
TextColor::WHITE)); .with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
}),
);
} }
fn close_on_esc( fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
mut commands: Commands, let mut transform = query.single_mut();
focused_windows: Query<(Entity, &Window)>,
input: Res<ButtonInput<KeyCode>>,
) {
for (window, focus) in focused_windows.iter() {
if !focus.focused {
continue;
}
if input.just_pressed(KeyCode::Escape) { transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
commands.entity(window).despawn(); }
}
fn update_config(mut config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>, time: Res<Time>) {
if keyboard.just_pressed(KeyCode::D) {
config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
}
if keyboard.just_pressed(KeyCode::P) {
// Toggle line_perspective
config.line_perspective ^= true;
// Increase the line width when line_perspective is on
config.line_width *= if config.line_perspective { 5. } else { 1. / 5. };
}
if keyboard.pressed(KeyCode::Right) {
config.line_width += 5. * time.delta_seconds();
}
if keyboard.pressed(KeyCode::Left) {
config.line_width -= 5. * time.delta_seconds();
} }
} }
fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) {
ambient_light.brightness = 100.0;
}

0
src/systems.rs Normal file
View file