Skip to content

Commit

Permalink
implement boids
Browse files Browse the repository at this point in the history
  • Loading branch information
reeceyang committed Dec 29, 2023
1 parent dffd9bb commit 39c6e81
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 177 deletions.
9 changes: 3 additions & 6 deletions native_app/src/rendering/entity_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl InstancedRender {
wagons_passenger: InstancedMeshBuilder::new(load_mesh(gfx, "wagon.glb").unwrap()),
trucks: InstancedMeshBuilder::new(load_mesh(gfx, "truck.glb").unwrap()),
pedestrians: InstancedMeshBuilder::new(load_mesh(gfx, "pedestrian.glb").unwrap()),
birds: InstancedMeshBuilder::new(load_mesh(gfx, "rail_freight_station.glb").unwrap()),
birds: InstancedMeshBuilder::new(load_mesh(gfx, "bird.glb").unwrap()),
}
}

Expand Down Expand Up @@ -97,11 +97,8 @@ impl InstancedRender {

for bird_ent in sim.world().birds.values() {
self.birds.instances.push(MeshInstance {
pos: bird_ent
.trans
.position
.up(0.5 + 0.4 * bird_ent.bird_mob.fly_anim.cos()),
dir: bird_ent.trans.dir.xy().z0(),
pos: bird_ent.trans.position,
dir: bird_ent.trans.dir,
tint: LinearColor::WHITE,
});
}
Expand Down
2 changes: 1 addition & 1 deletion simulation/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub fn init() {
register_system("random_vehicles", random_vehicles_update);

register_system_sim("add_souls_to_empty_buildings", add_souls_to_empty_buildings);
register_system_sim("add_birds_randomly", add_flocks_randomly);
register_system_sim("add_flocks_randomly", add_flocks_randomly);

register_resource_noserialize::<GoodsCompanyRegistry>();
register_resource_noserialize::<ItemRegistry>();
Expand Down
252 changes: 94 additions & 158 deletions simulation/src/wildlife/bird.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,33 @@
use crate::map::Map;
use crate::map_dynamic::Itinerary;
use crate::physics::Speed;
use crate::utils::rand_provider::RandProvider;
use crate::utils::resources::Resources;
use crate::utils::time::GameTime;
use crate::Simulation;
use crate::World;
use crate::{BirdEnt, BirdID};
use common::rand::rand2;
use egui_inspect::Inspect;
use geom::angle_lerpxy;
use geom::AABB;
use geom::{Transform, Vec3};
use serde::{Deserialize, Serialize};

pub fn spawn_bird(sim: &mut Simulation, home_pos: Vec3) -> Option<BirdID> {
pub fn spawn_bird(sim: &mut Simulation, spawn_pos: Vec3) -> Option<BirdID> {
profiling::scope!("spawn_bird");

// log::info!("added bird at {}", home_pos);

let mob = BirdMob::new(&mut sim.write::<RandProvider>());
log::info!("added bird at {}", spawn_pos);

let id = sim.world.insert(BirdEnt {
trans: Transform::new(home_pos),
bird_mob: mob,
it: Itinerary::simple(vec![Vec3 {
x: home_pos.x,
y: home_pos.y,
z: home_pos.z,
}]),
trans: Transform::new(spawn_pos),
speed: Speed::default(),
});

Some(id)
}

#[derive(Serialize, Deserialize, Inspect)]
pub struct BirdMob {
pub flying_speed: f32,
pub fly_anim: f32,
pub flock_id: u32,
}

pub const NUM_FLOCKS: u32 = 10;

impl BirdMob {
pub(crate) fn new(r: &mut RandProvider) -> Self {
Self {
flying_speed: (10.0 + r.next_f32() * 0.4),
fly_anim: 0.0,
flock_id: (NUM_FLOCKS as f32 * r.next_f32()) as u32,
}
}
}

pub fn bird_decision_system(world: &mut World, resources: &mut Resources) {
profiling::scope!("wildlife::animal_decision_system");
let ra = &*resources.read::<GameTime>();
let map = &*resources.read::<Map>();

let aabb = map.terrain.bounds();

Check failure on line 30 in simulation/src/wildlife/bird.rs

View workflow job for this annotation

GitHub Actions / build

no field `terrain` on type `&map::map::Map`
let r = &mut RandProvider::new(ra.timestamp as u64);

world.flocks.values().for_each(|flock| {
let flock_physics: Vec<(Transform, Speed)> = flock
Expand All @@ -72,155 +39,124 @@ pub fn bird_decision_system(world: &mut World, resources: &mut Resources) {
})
.collect();

let flock_center = center(&flock_physics);
let flock_avg_v = average_velocity(&flock_physics);

flock
.bird_ids
.iter()
.for_each(|bird_id| match world.birds.get_mut(*bird_id) {
Some(bird_ent) => bird_decision(
ra,
&mut bird_ent.it,
&mut bird_ent.trans,
&mut bird_ent.speed,
&mut bird_ent.bird_mob,
flock_center,
flock_avg_v,
aabb,
r,
&flock_physics,
),
None => unreachable!(),
})
});

// world.birds
// .values_mut()
// //.par_bridge()
// .for_each(|bird| bird_decision(ra, &mut bird.it, &mut bird.trans, &mut bird.speed, &mut bird.bird_mob, aabb, r))
}

const BIRD_WAIT_TIME: f64 = 100.0;

pub fn get_random_bird_pos(aabb: AABB, r1: f32, r2: f32, r3: f32) -> Vec3 {
let AABB { ll, ur } = aabb;
Vec3 {
x: ll.x + (ur.x - ll.x) * r1,
y: ll.y + (ur.y - ll.y) * r2,
z: 2.0 + 10.0 * r3,
}
}

pub fn get_random_pos_from_center(center: Vec3, radius: f32, r1: f32, r2: f32, r3: f32) -> Vec3 {
Vec3 {
x: center.x + radius * (r1 - 0.5),
y: center.y + radius * (r2 - 0.5),
z: 2.0 + 10.0 * r3,
}
}

// amount of time per each flock itinerary
const FLOCK_IT_PERIOD: f32 = 20000.0;
// likelihood the bird will stray from the flock itinerary
const DISTRACTED_PROB: f32 = 0.5;

/// Update the speed, position, and direction of a bird
pub fn bird_decision(
time: &GameTime,
it: &mut Itinerary,
trans: &mut Transform,
kin: &mut Speed,
bird_mob: &mut BirdMob,
flock_center: Vec3,
flock_avg_v: Vec3,
aabb: AABB,
r: &mut RandProvider,
flock_physics: &Vec<(Transform, Speed)>,
) {
let (desired_v, desired_dir) = calc_decision(bird_mob, trans, it);

bird_mob.fly_anim += 2.0 * kin.0 * time.realdelta / bird_mob.flying_speed;
bird_mob.fly_anim %= 2.0 * std::f32::consts::PI;
physics(kin, trans, time, desired_v, desired_dir);

// a random nearby location to hang around
let random_itinerary = Itinerary::simple(vec![get_random_pos_from_center(
trans.position,
100.0,
r.next_f32(),
r.next_f32(),
r.next_f32(),
)]);

// every bird in the flock should have the same itinerary during the same flock period
let flock_itinerary = Itinerary::simple(vec![get_random_bird_pos(
aabb,
rand2(
bird_mob.flock_id as f32,
(time.timestamp as f32 / FLOCK_IT_PERIOD).floor(),
),
rand2(
(time.timestamp as f32 / FLOCK_IT_PERIOD).floor(),
bird_mob.flock_id as f32,
),
r.next_f32(),
)]);

// choose a random new destination if the current one has been reached
if it.has_ended(time.timestamp) {
if it.is_none_or_wait() {
*it = if r.next_f32() < DISTRACTED_PROB {
random_itinerary
} else {
flock_itinerary
};
} else {
// wait a bit before continuing
*it = Itinerary::wait_until(time.timestamp + BIRD_WAIT_TIME);
}
}
}
// the initial velocity of the bird
let mut dv = trans.dir * kin.0;

const BIRD_ACC: f32 = 1.5;
// fly towards the flock center
const CENTERING_FACTOR: f32 = 0.1;
dv += (flock_center - trans.position) * CENTERING_FACTOR;

pub fn physics(
kin: &mut Speed,
trans: &mut Transform,
time: &GameTime,
desired_velocity: f32,
desired_dir: Vec3,
) {
let diff = desired_velocity - kin.0;
let mag = diff.min(time.realdelta * BIRD_ACC);
if mag > 0.0 {
kin.0 += mag;
// match the flock's average velocity
const MATCHING_FACTOR: f32 = 0.1;
dv += (flock_avg_v - dv) * MATCHING_FACTOR;

// avoid nearby birds
const AVOID_FACTOR: f32 = 0.1;
dv += separation_adjustment(trans, flock_physics) * AVOID_FACTOR;

// avoid map boundaries
dv += bounds_adjustment(trans, aabb);

// cap the speed of the bird
const SPEED_LIMIT: f32 = 10.0;
if dv.mag() > SPEED_LIMIT {
dv = dv.normalize_to(SPEED_LIMIT);
}

// update the bird's speed, position, and direction
const ANG_VEL: f32 = 1.0;
// log::info!("{} {}", trans.dir, desired_dir);
assert!(
desired_dir.xy().mag() != 0.0,
"desired_dir.xy() had 0 magnitude"
);
trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * time.realdelta);
trans.dir = angle_lerpxy(trans.dir, dv, ANG_VEL * time.realdelta).normalize();
kin.0 = dv.mag();
trans.position += dv * time.realdelta;
}

pub fn calc_decision(bird_mob: &mut BirdMob, trans: &Transform, it: &Itinerary) -> (f32, Vec3) {
assert!(
trans.dir.xy().mag() != 0.0,
"trans.dir.xy() had 0 magnitude"
);
let objective = match it.get_point() {
Some(x) => x,
None => return (0.0, trans.dir),
};

let position = trans.position;

let delta_pos: Vec3 = objective - position;
let dir_to_pos = match delta_pos.xy().try_normalize() {
Some(x) => x.z(trans.dir.z),
None => return (0.0, trans.dir),
};

// log::info!("calc_decision trans.dir {}", trans.dir);
// log::info!("calc_decision dir_to_pos {}", dir_to_pos);
let desired_dir = dir_to_pos.normalize();
assert!(
desired_dir.xy().mag() != 0.0,
"desired_dir.xy() had 0 magnitude in calc_decision"
);
(bird_mob.flying_speed, desired_dir)
/// Calculate the center of the flock (the average position of the flock)
fn center(flock_physics: &Vec<(Transform, Speed)>) -> Vec3 {
flock_physics
.iter()
.map(|(t, _)| t.position)
// TODO: use .sum() ?
.reduce(|a, b| a + b).unwrap()
/ flock_physics.len() as f32
}

/// Calculate the average velocity of the flock
fn average_velocity(flock_physics: &Vec<(Transform, Speed)>) -> Vec3 {
flock_physics
.iter()
.map(|(t, s)| t.dir.normalize() * s.0)
// TODO: use .sum() ?
.reduce(|a, b| a + b).unwrap()
/ flock_physics.len() as f32
}

/// Get an adjustment vector to move the bird away from other birds
fn separation_adjustment(trans: &Transform, flock_physics: &Vec<(Transform, Speed)>) -> Vec3 {
const MIN_DISTANCE: f32 = 20.0;
flock_physics
.iter()
.filter(|(t, _)| t.position.distance(trans.position) < MIN_DISTANCE)
.map(|(t, _)| trans.position - t.position)
// TODO: use .sum() ?
.reduce(|a, b| a + b)
.unwrap()
}

/// Get an adjustment vector to move the bird away from the map bounds
fn bounds_adjustment(trans: &Transform, aabb: AABB) -> Vec3 {
const MARGIN: f32 = 2.0;
const MAX_Z: f32 = 200.0;
const TURN_AMOUNT: f32 = 1.0;
let mut v = Vec3::new(0.0, 0.0, 0.0);
// TODO: the ground might not be at 0
if trans.position.z < MARGIN {
v.z += TURN_AMOUNT;
}
if trans.position.z > MAX_Z - MARGIN {
v.z -= TURN_AMOUNT;
}
if trans.position.x < aabb.ll.x + MARGIN {
v.x += TURN_AMOUNT;
}
if trans.position.x > aabb.ur.x - MARGIN {
v.x -= TURN_AMOUNT;
}
if trans.position.y < aabb.ll.y + MARGIN {
v.y += TURN_AMOUNT;
}
if trans.position.y > aabb.ur.y - MARGIN {
v.y -= TURN_AMOUNT;
}
v
}
Loading

0 comments on commit 39c6e81

Please sign in to comment.