Compare commits

..

No commits in common. "279ebaa4c16faf3491d0b88bae5e99b82944e91b" and "51756046491a96080a70080bad0cb1eab1fc191e" have entirely different histories.

7 changed files with 1565 additions and 3820 deletions

View file

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

4939
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,12 @@
[package]
name = "toids"
name = "audubon"
version = "0.1.0"
edition = "2024"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
argh = "0.1.12"
bevy = { version = "0.19", default-features = false, features = ["default_app", "default_platform", "bevy_scene", "3d_bevy_render", "bevy_ui"] }
crossbeam-channel = "0.5"
rand = { version = "0.10", features = ["thread_rng"] }
rstar = "0.13"
bevy = "0.12.0"
rand = "0.8.5"
rstar = "0.11.0"

Binary file not shown.

View file

@ -1,283 +1,123 @@
use std::collections::{HashMap, HashSet};
use argh::FromArgs;
use bevy::{input::keyboard::Key, prelude::*, tasks::AsyncComputeTaskPool};
use crossbeam_channel::{bounded, Receiver, Sender};
use rand::RngExt;
use rstar::{primitives::GeomWithData, RTree};
use bevy::prelude::*;
type Index = RTree<GeomWithData<[f32; 3], Entity>>;
// toid stuff
const SPEED: f32 = 2.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
const RADIUS: f32 = 40.0;
// how close to try to stay to your buddies
const BUDDY_RADIUS: f32 = 10.0;
const MIN_ALTITUDE: f32 = 3.5;
const SPEED: f32 = 20.0;
const SPEED_DIFF_RANGE: f32 = 0.08; // +/- 8%
#[derive(Debug, FromArgs, Resource)]
/// Toid Watching
pub struct Config {
/// how many Toids to spawn
#[argh(option, short = 't', default = "5_000")]
#[argh(option, short = 't', default = "100")]
pub toids: usize,
}
#[derive(Clone, Debug, Default, Deref, DerefMut, Resource)]
pub struct Positions(pub HashMap<Entity, Vec3>);
pub type Point = (f32, f32, f32);
#[derive(Component, Debug, Deref, DerefMut, Clone, Default)]
pub struct Buddies(HashSet<Entity>);
pub type Toint = rstar::primitives::GeomWithData<Point, Entity>;
pub trait Pointable {
fn to_point(&self) -> Point;
}
impl Pointable for Vec3 {
fn to_point(&self) -> Point {
(self.x, self.y, self.z)
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct Index(pub rstar::RTree<Toint>);
#[derive(Resource, Deref, DerefMut)]
pub struct Positions(pub std::collections::HashMap<Entity, Vec3>);
#[derive(Component, Debug, Clone, Deref, DerefMut, Default)]
pub struct Buddies(Vec<Entity>);
#[derive(Component, Debug, Clone, Deref, DerefMut, Default)]
pub struct Velocity(Vec3);
#[derive(Debug, Default, Resource)]
pub struct Naybs {
index: Index,
positions: Option<Box<[Vec3]>>,
}
#[derive(Resource)]
pub struct RawPosChannel {
tx: Sender<Box<[Vec3]>>,
rx: Receiver<Box<[Vec3]>>,
}
#[derive(Resource)]
pub struct IndexChannel {
tx: Sender<(Box<[Vec3]>, Index)>,
rx: Receiver<(Box<[Vec3]>, Index)>,
}
#[derive(Component)]
pub struct Toid {
pub speed: f32,
pub buddies: usize,
}
#[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)]
pub struct LookAt(Vec3);
pub fn turkey_time(
commands: &mut Commands,
scene: &Handle<Scene>,
r: &mut impl rand::prelude::Rng,
assets: &AssetServer,
) -> Entity {
let speed_diff = r.random_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
let speed_diff = r.gen_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
let speed = SPEED + (SPEED * speed_diff);
let vel = unit_vec(r) * speed;
let buddies = r.random_range(6..=8);
let x = r.random_range(-10.0..=10.0);
let z = r.random_range(-10.0..=10.0);
let y = r.random_range(MIN_ALTITUDE..=(MIN_ALTITUDE + 5.5));
commands
.spawn((
Velocity(vel),
Buddies::default(),
Toid { speed, buddies },
Transform::from_xyz(x, y, z),
))
.spawn(SpatialBundle::default())
.insert((Velocity(vel), Buddies::default(), Toid { speed }))
.with_children(|t| {
t.spawn(WorldAssetRoot(
assets.load(GltfAssetLabel::Scene(0).from_asset("models/toid.glb")),
))
.insert(Transform::default());
// .insert(Transform::from_rotation(Quat::from_axis_angle(
// Vec3::Y,
// -std::f32::consts::FRAC_PI_2,
// )));
t.spawn(SceneBundle {
scene: scene.to_owned(),
..Default::default()
})
.insert(Transform::from_rotation(Quat::from_axis_angle(
Vec3::Y,
-std::f32::consts::FRAC_PI_2,
)));
})
.id()
}
pub fn update_vel(
mut toids: Query<(&Transform, &mut Velocity, &Toid, &Buddies, Entity)>,
time: Res<Time>,
positions: Res<Positions>,
index: Res<Naybs>,
) {
let dt = time.delta_secs();
let max_delta = MAX_DELTA_V * dt;
for (xform, mut vel, toid, buddies, entity) in toids.iter_mut() {
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.0.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;
for neighbor in index
.index
.locate_within_distance(pos.to_array(), min_dist)
.filter(|n| n.data != entity)
{
let bp = Vec3::from_array(*neighbor.geom());
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 add_gizmos(mut gizmos: Gizmos, toids: Query<(&Transform, Entity), With<Toid>>) {
let gizmos = &mut gizmos;
for (pos, _entity) in toids.iter() {
let nudge = pos.up() * 0.15;
let rpos = pos.translation + nudge;
gizmos.ray(rpos, pos.forward(), Color::RED);
}
}
pub fn update_pos(
mut toids: Query<(&mut Transform, &Velocity, Entity), With<Toid>>,
mut positions: ResMut<Positions>,
mut lookat: ResMut<LookAt>,
mut toids: Query<(&mut Transform, &Velocity, Entity)>,
time: Res<Time>,
mut index: Res<Index>,
) {
let dt = time.delta_seconds();
for (mut xform, vel, _entity) in toids.iter_mut() {
let vel = vel.0;
xform.translation += vel * dt;
}
}
pub fn update_vel(
mut toids: Query<(&Transform, &mut Velocity, &Toid), With<Toid>>,
time: Res<Time>,
) {
let mut new_look = Vec3::ZERO;
let dt = time.delta_secs();
for (mut xform, vel, entity) in toids.iter_mut() {
let look_at = xform.translation + vel.0;
xform.translation += vel.0 * dt;
xform.look_at(look_at, Vec3::Y);
*positions.entry(entity).or_insert(Vec3::ZERO) = xform.translation;
new_look += xform.translation;
}
**lookat = new_look / positions.len() as f32;
}
let dt = time.delta_seconds();
for (xform, mut vel, toid) in toids.iter_mut() {
let speed = toid.speed;
let mut dir = xform.forward();
pub fn update_buddies(
mut toids: Query<(&Transform, Entity, &Toid, &mut Buddies)>,
index: Res<Naybs>,
positions: Res<Positions>,
) {
let d2 = (BUDDY_RADIUS * 1.5).powi(2);
for (xform, entity, toid, mut buddies) in toids.iter_mut() {
let pos = xform.translation;
for buddy in buddies.clone().iter() {
let bp = positions[buddy];
let bd2 = (bp - pos).length_squared();
if bd2 > d2 {
buddies.remove(buddy);
// nudge up if too low
// nudge toward origin if too far
// find buddies and orient: point more towards further-away buddies
}
}
if buddies.len() < toid.buddies {
let diff = toid.buddies - buddies.len();
for neighbor in index
.index
.nearest_neighbor_iter(pos.to_array())
.filter(|n| n.data != entity)
.take(diff)
{
buddies.insert(neighbor.data);
}
}
}
}
pub fn update_gizmos(toids: Query<&Transform, With<Toid>>, mut gizmos: Gizmos) {
for toid in toids.iter() {
let pos = toid.translation;
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(1., 0., 0.));
gizmos.ray(pos, pos + up, Color::srgb(0., 1., 0.));
gizmos.ray(pos, pos + right, Color::srgb(0., 0., 1.));
}
}
pub fn rotate_camera(
mut query: Query<&mut Transform, With<Camera>>,
keys: Res<ButtonInput<Key>>,
lookat: Res<LookAt>,
) {
let mut xform = query.single_mut().unwrap();
let forward = xform.forward().as_vec3() * 0.7;
let right = xform.right().as_vec3();
if keys.pressed(Key::ArrowRight) {
xform.translation += right;
}
if keys.pressed(Key::ArrowLeft) {
xform.translation -= right;
}
if keys.pressed(Key::ArrowUp) {
if keys.pressed(Key::Shift) {
xform.translation += Vec3::Y;
} else {
xform.translation += forward;
}
}
if keys.pressed(Key::ArrowDown) {
if keys.pressed(Key::Shift) {
xform.translation -= Vec3::Y;
} else {
xform.translation -= forward;
}
}
xform.look_at(**lookat, Vec3::Y);
}
pub fn update_buddies(mut toids: Query<(&Transform, Entity, &mut Buddies)>, index: Res<Index>) {}
//-************************************************************************
// util
//-************************************************************************
pub fn unit_vec(r: &mut impl rand::Rng) -> Vec3 {
pub fn unit_vec(r: &mut impl rand::prelude::Rng) -> Vec3 {
let mut x1: f32 = 0.0;
let mut x2: f32 = 0.0;
let mut ssum = f32::MAX;
let mut ssum = std::f32::MAX;
while ssum >= 1.0 {
x1 = r.random_range(-1.0..=1.0);
x2 = r.random_range(-1.0..=1.0);
x1 = r.gen_range(-1.0..=1.0);
x2 = r.gen_range(-1.0..=1.0);
ssum = x1.powi(2) + x2.powi(2);
}
let sqrt = (1.0 - ssum).sqrt();

View file

@ -1,35 +1,24 @@
use std::time::Duration;
//use std::f32::consts::PI;
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
input::keyboard::Key,
prelude::*,
};
use toids::{
rotate_camera, turkey_time, update_buddies, update_pos, update_vel, Config, LookAt, Positions,
};
use bevy::prelude::*;
use audubon::*;
fn main() {
let config: toids::Config = argh::from_env();
let config: audubon::Config = argh::from_env();
App::new()
.add_plugins(DefaultPlugins)
.add_plugins((
FrameTimeDiagnosticsPlugin::default(),
LogDiagnosticsPlugin::default(),
))
.insert_resource(config)
.insert_resource(Positions::default())
.insert_resource(ClearColor(Color::srgb(0.64, 0.745, 0.937))) // a nice light blue
.insert_resource(GlobalAmbientLight {
color: Color::WHITE,
brightness: 1.0,
..Default::default()
})
.insert_resource(LookAt::default())
.add_systems(Startup, setup)
.add_systems(Update, (update_pos, update_buddies, update_vel).chain())
.add_systems(Update, (rotate_camera, close_on_esc))
//.add_systems(Update, update_gizmos)
.add_systems(
Update,
(
add_gizmos,
rotate_camera,
update_config,
bevy::window::close_on_esc,
),
)
.run();
}
@ -40,55 +29,77 @@ fn setup(
config: Res<Config>,
models: Res<AssetServer>,
) {
let rand = &mut rand::rng();
let rand = &mut rand::thread_rng();
commands.spawn((
Transform::from_xyz(0., 5.0, 25.).looking_at(Vec3::new(0.0, 5.0, 0.0), Vec3::Y),
Camera3d::default(),
));
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0., 1.5, 6.).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
// plane
commands.spawn((
Mesh3d(meshes.add(Mesh::from(Plane3d::new(Vec3::Y, Vec2::splat(500.0))))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
Transform::default(),
));
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane::from_size(50.0))),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
// let toid_model = models.load("models/boid.glb#Scene0");
let toid_model = models.load("models/boid.glb#Scene0");
for _ in 0..config.toids {
let _ = turkey_time(&mut commands, rand, &models);
let _ = turkey_time(&mut commands, &toid_model, rand);
}
// instructions
commands.spawn((
Node {
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// 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()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: px(12.0),
left: px(12.0),
top: Val::Px(12.0),
left: Val::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()
},
)],
));
}),
);
}
fn close_on_esc(
mut commands: Commands,
focused_windows: Query<(Entity, &Window)>,
input: Res<ButtonInput<Key>>,
) {
for (window, focus) in focused_windows.iter() {
if !focus.focused {
continue;
fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
let mut transform = query.single_mut();
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
}
if input.just_pressed(Key::Escape) {
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();
}
}

0
src/systems.rs Normal file
View file