From 1808f136f5b713e9a0bbbcb34937dc8b8e979036 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Fri, 22 Dec 2023 15:14:49 -0600 Subject: [PATCH 01/12] add birds --- assets/models/bird.glb | 3 + native_app/src/gui/inspect/inspect_debug.rs | 7 +- native_app/src/gui/selectable.rs | 1 + native_app/src/rendering/entity_render.rs | 14 +++ simulation/src/init.rs | 4 + simulation/src/lib.rs | 1 + simulation/src/transportation/bird_mob.rs | 122 ++++++++++++++++++++ simulation/src/transportation/mod.rs | 1 + simulation/src/wildlife/bird.rs | 28 +++++ simulation/src/wildlife/mod.rs | 24 ++++ simulation/src/world.rs | 24 ++++ 11 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 assets/models/bird.glb create mode 100644 simulation/src/transportation/bird_mob.rs create mode 100644 simulation/src/wildlife/bird.rs create mode 100644 simulation/src/wildlife/mod.rs diff --git a/assets/models/bird.glb b/assets/models/bird.glb new file mode 100644 index 00000000..d7807ebe --- /dev/null +++ b/assets/models/bird.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b9b7cc1785ff6313ceab5d71e51cba6768d124078b2414e4c6149b2ce9f4860 +size 44280 diff --git a/native_app/src/gui/inspect/inspect_debug.rs b/native_app/src/gui/inspect/inspect_debug.rs index 8050a85f..0f7dd1ea 100644 --- a/native_app/src/gui/inspect/inspect_debug.rs +++ b/native_app/src/gui/inspect/inspect_debug.rs @@ -5,8 +5,8 @@ use egui_inspect::{Inspect, InspectArgs}; use simulation::economy::{ItemRegistry, Market}; use simulation::transportation::Location; use simulation::{ - AnyEntity, CompanyEnt, FreightStationEnt, HumanEnt, Simulation, SoulID, TrainEnt, VehicleEnt, - WagonEnt, + AnyEntity, BirdEnt, CompanyEnt, FreightStationEnt, HumanEnt, Simulation, SoulID, TrainEnt, + VehicleEnt, WagonEnt, }; /// Inspect window @@ -49,6 +49,9 @@ impl InspectRenderer { AnyEntity::HumanID(x) => { >::render(sim.get(x).unwrap(), "", ui, &args) } + AnyEntity::BirdID(x) => { + >::render(sim.get(x).unwrap(), "", ui, &args) + } } if let AnyEntity::VehicleID(id) = entity { diff --git a/native_app/src/gui/selectable.rs b/native_app/src/gui/selectable.rs index cddecf1e..4305b2f9 100644 --- a/native_app/src/gui/selectable.rs +++ b/native_app/src/gui/selectable.rs @@ -13,6 +13,7 @@ pub fn select_radius(id: AnyEntity) -> f32 { AnyEntity::FreightStationID(_) => 0.0, AnyEntity::CompanyID(_) => 0.0, AnyEntity::HumanID(_) => 3.0, + AnyEntity::BirdID(_) => 3.0, } } diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index b942eff8..4b2db2fb 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -14,6 +14,7 @@ pub struct InstancedRender { pub wagons_freight: InstancedMeshBuilder, pub trucks: InstancedMeshBuilder, pub pedestrians: InstancedMeshBuilder, + pub birds: InstancedMeshBuilder, } impl InstancedRender { @@ -32,6 +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, "bird.glb").unwrap()), } } @@ -40,6 +42,7 @@ impl InstancedRender { self.cars.instances.clear(); self.trucks.instances.clear(); self.pedestrians.instances.clear(); + self.birds.instances.clear(); for v in sim.world().vehicles.values() { let trans = &v.trans; let instance = MeshInstance { @@ -92,6 +95,14 @@ impl InstancedRender { } } + for animal in sim.world().birds.values() { + self.birds.instances.push(MeshInstance { + pos: animal.trans.position, + dir: animal.trans.dir.xy().z0(), + tint: LinearColor::WHITE, + }); + } + self.path_not_found.clear(); for (_, (trans, itin)) in sim.world().query_trans_itin() { let Some(wait) = itin.is_wait_for_reroute() else { @@ -125,6 +136,9 @@ impl InstancedRender { if let Some(x) = self.pedestrians.build(fctx.gfx) { fctx.objs.push(Box::new(x)); } + if let Some(x) = self.birds.build(fctx.gfx) { + fctx.objs.push(Box::new(x)); + } if let Some(x) = self.locomotives.build(fctx.gfx) { fctx.objs.push(Box::new(x)); } diff --git a/simulation/src/init.rs b/simulation/src/init.rs index 43e678aa..1d3474d9 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -9,6 +9,7 @@ use crate::physics::coworld_synchronize; use crate::souls::freight_station::freight_station_system; use crate::souls::goods_company::{company_system, GoodsCompanyRegistry}; use crate::souls::human::update_decision_system; +use crate::transportation::bird_mob::bird_decision_system; use crate::transportation::pedestrian_decision_system; use crate::transportation::road::{vehicle_decision_system, vehicle_state_update_system}; use crate::transportation::testing_vehicles::{random_vehicles_update, RandomVehicles}; @@ -17,6 +18,7 @@ use crate::transportation::train::{ }; use crate::utils::resources::Resources; use crate::utils::time::Tick; +use crate::wildlife::add_birds_randomly; use crate::world::{CompanyEnt, FreightStationEnt, HumanEnt, TrainEnt, VehicleEnt, WagonEnt}; use crate::World; use crate::{ @@ -33,6 +35,7 @@ pub fn init() { register_system("update_decision_system", update_decision_system); register_system("company_system", company_system); register_system("pedestrian_decision_system", pedestrian_decision_system); + register_system("bird_decision_system", bird_decision_system); register_system("coworld_synchronize", coworld_synchronize); register_system("locomotive_system", locomotive_system); register_system("vehicle_decision_system", vehicle_decision_system); @@ -46,6 +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_birds_randomly); register_resource_noserialize::(); register_resource_noserialize::(); diff --git a/simulation/src/lib.rs b/simulation/src/lib.rs index 838874a2..9dd7a852 100644 --- a/simulation/src/lib.rs +++ b/simulation/src/lib.rs @@ -46,6 +46,7 @@ pub mod souls; mod tests; pub mod transportation; pub mod utils; +pub mod wildlife; mod world; pub mod world_command; diff --git a/simulation/src/transportation/bird_mob.rs b/simulation/src/transportation/bird_mob.rs new file mode 100644 index 00000000..ef3d7e10 --- /dev/null +++ b/simulation/src/transportation/bird_mob.rs @@ -0,0 +1,122 @@ +use crate::map::Map; +use crate::map_dynamic::Itinerary; +use crate::physics::{Collider, CollisionWorld, PhysicsGroup, PhysicsObject, Speed}; +use crate::utils::rand_provider::RandProvider; +use crate::utils::resources::Resources; +use crate::utils::time::GameTime; +use crate::World; +use common::rand::rand3; +use egui_inspect::Inspect; +use geom::{angle_lerpxy, Transform, Vec3}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Inspect)] +pub struct BirdMob { + pub walking_speed: f32, + pub walk_anim: f32, +} + +const PED_SIZE: f32 = 0.5; + +pub fn put_bird_in_coworld(coworld: &mut CollisionWorld, pos: Vec3) -> Collider { + Collider(coworld.insert( + pos.xy(), + PhysicsObject { + radius: PED_SIZE * 0.6, + group: PhysicsGroup::Unknown, + ..Default::default() + }, + )) +} + +impl BirdMob { + pub(crate) fn new(r: &mut RandProvider) -> Self { + Self { + walking_speed: (10.0 + r.next_f32() * 0.4), + walk_anim: 0.0, + } + } +} + +pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { + profiling::scope!("transportation::animal_decision_system"); + let ra = &*resources.read(); + let map = &*resources.read::(); + let next_dests: Vec = map + .buildings() + .values() + .map(|building| building.door_pos.up(building.height)) + // .chain( + // map.terrain + // .trees + // .objects() + // .map(|tree| vec3(tree.1.pos.x, tree.1.pos.y, tree.1.size)), + // ) + .collect(); + world.birds + .values_mut() + //.par_bridge() + .for_each(|human| bird_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.bird_mob, &next_dests)) +} + +pub fn bird_decision( + time: &GameTime, + it: &mut Itinerary, + trans: &mut Transform, + kin: &mut Speed, + bird_mob: &mut BirdMob, + next_dests: &Vec, +) { + let (desired_v, desired_dir) = calc_decision(bird_mob, trans, it); + + bird_mob.walk_anim += 7.0 * kin.0 * time.realdelta / bird_mob.walking_speed; + bird_mob.walk_anim %= 2.0 * std::f32::consts::PI; + physics(kin, trans, time, desired_v, desired_dir); + + if let Some(end_pos) = it.end_pos() { + if trans.position.is_close(end_pos, 3.0) { + // choose a random new destination + *it = Itinerary::simple(vec![ + next_dests[(next_dests.len() as f32 + * rand3(trans.position.x, trans.position.y, time.timestamp as f32)) + as usize], + ]); + } + } +} + +const BIRD_ACC: f32 = 1.5; + +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; + } + const ANG_VEL: f32 = 1.0; + trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * time.realdelta); +} + +pub fn calc_decision(bird_mob: &mut BirdMob, trans: &Transform, it: &Itinerary) -> (f32, Vec3) { + 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.try_normalize() { + Some(x) => x, + None => return (0.0, trans.dir), + }; + + let desired_dir = dir_to_pos.normalize(); + (bird_mob.walking_speed, desired_dir) +} diff --git a/simulation/src/transportation/mod.rs b/simulation/src/transportation/mod.rs index e23d6aae..06ce7a67 100644 --- a/simulation/src/transportation/mod.rs +++ b/simulation/src/transportation/mod.rs @@ -1,6 +1,7 @@ use crate::map::BuildingID; use serde::{Deserialize, Serialize}; +pub mod bird_mob; pub mod pedestrian; pub mod road; pub mod testing_vehicles; diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs new file mode 100644 index 00000000..0bd5635a --- /dev/null +++ b/simulation/src/wildlife/bird.rs @@ -0,0 +1,28 @@ +use crate::map_dynamic::Itinerary; +use crate::physics::Speed; +use crate::transportation::bird_mob::BirdMob; +use crate::utils::rand_provider::RandProvider; +use crate::Simulation; +use crate::{BirdEnt, BirdID}; +use geom::{Transform, Vec3}; + +pub fn spawn_bird(sim: &mut Simulation, home_pos: Vec3) -> Option { + profiling::scope!("spawn_bird"); + + log::info!("added bird at {}", home_pos); + + let a = BirdMob::new(&mut sim.write::()); + + let id = sim.world.insert(BirdEnt { + trans: Transform::new(home_pos), + bird_mob: a, + it: Itinerary::simple(vec![Vec3 { + x: home_pos.x, + y: home_pos.y, + z: home_pos.z, + }]), + speed: Speed::default(), + }); + + Some(id) +} diff --git a/simulation/src/wildlife/mod.rs b/simulation/src/wildlife/mod.rs new file mode 100644 index 00000000..1bab5c24 --- /dev/null +++ b/simulation/src/wildlife/mod.rs @@ -0,0 +1,24 @@ +use crate::wildlife::bird::spawn_bird; +use crate::Simulation; +use common::rand::{rand, rand2}; +use geom::Vec3; + +pub mod bird; + +/// HACK (for now): add no more than 50 birds to random locations +pub(crate) fn add_birds_randomly(sim: &mut Simulation) { + profiling::scope!("wildlife::add_birds_randomly"); + + let num_birds = sim.world().birds.len(); + if num_birds > 50 { + return; + } + + let home_pos = Vec3 { + x: 5000.0 + 1000.0 * rand(num_birds as f32), + y: 5000.0 + 1000.0 * rand2(num_birds as f32, num_birds as f32), + z: 0.0, + }; + + spawn_bird(sim, home_pos); +} diff --git a/simulation/src/world.rs b/simulation/src/world.rs index d7661ecf..5e73d99b 100644 --- a/simulation/src/world.rs +++ b/simulation/src/world.rs @@ -8,6 +8,7 @@ use crate::souls::desire::{BuyFood, Home, Work}; use crate::souls::freight_station::FreightStation; use crate::souls::goods_company::GoodsCompany; use crate::souls::human::{HumanDecision, PersonalInfo}; +use crate::transportation::bird_mob::BirdMob; use crate::transportation::train::{Locomotive, LocomotiveReservation, RailWagon}; use crate::transportation::{Location, Pedestrian, Vehicle, VehicleKind, VehicleState}; use crate::utils::par_command_buffer::SimDrop; @@ -27,6 +28,7 @@ new_key_type! { pub struct WagonID; pub struct FreightStationID; pub struct CompanyID; + pub struct BirdID; } impl_entity!(VehicleID, VehicleEnt, vehicles); @@ -35,6 +37,7 @@ impl_entity!(TrainID, TrainEnt, trains); impl_entity!(WagonID, WagonEnt, wagons); impl_entity!(FreightStationID, FreightStationEnt, freight_stations); impl_entity!(CompanyID, CompanyEnt, companies); +impl_entity!(BirdID, BirdEnt, birds); impl_trans!(HumanID); impl_trans!(VehicleID); @@ -42,6 +45,7 @@ impl_trans!(TrainID); impl_trans!(WagonID); impl_trans!(FreightStationID); impl_trans!(CompanyID); +impl_trans!(BirdID); #[derive(PartialEq, Eq, Copy, Clone, Debug, From, TryInto)] pub enum AnyEntity { @@ -51,6 +55,7 @@ pub enum AnyEntity { FreightStationID(FreightStationID), CompanyID(CompanyID), HumanID(HumanID), + BirdID(BirdID), } #[derive(Inspect, Serialize, Deserialize)] @@ -177,6 +182,18 @@ impl SimDrop for CompanyEnt { } } +#[derive(Inspect, Serialize, Deserialize)] +pub struct BirdEnt { + pub trans: Transform, + pub speed: Speed, + pub bird_mob: BirdMob, + pub it: Itinerary, +} + +impl SimDrop for BirdEnt { + fn sim_drop(self, _: BirdID, _: &mut Resources) {} +} + #[derive(Default, Serialize, Deserialize)] pub struct World { pub vehicles: HopSlotMap, @@ -185,6 +202,7 @@ pub struct World { pub wagons: HopSlotMap, pub freight_stations: HopSlotMap, pub companies: HopSlotMap, + pub birds: HopSlotMap, } impl World { @@ -212,6 +230,7 @@ impl World { AnyEntity::FreightStationID(id) => self.storage_id(id).contains_key(id), AnyEntity::CompanyID(id) => self.storage_id(id).contains_key(id), AnyEntity::HumanID(id) => self.storage_id(id).contains_key(id), + AnyEntity::BirdID(id) => self.storage_id(id).contains_key(id), } } @@ -221,6 +240,7 @@ impl World { AnyEntity::TrainID(x) => self.pos(x), AnyEntity::WagonID(x) => self.pos(x), AnyEntity::HumanID(x) => self.pos(x), + AnyEntity::BirdID(x) => self.pos(x), _ => None, } } @@ -230,6 +250,7 @@ impl World { AnyEntity::VehicleID(x) => Some(&self.get(x)?.it), AnyEntity::TrainID(x) => Some(&self.get(x)?.it), AnyEntity::HumanID(x) => Some(&self.get(x)?.it), + AnyEntity::BirdID(x) => Some(&self.get(x)?.it), _ => None, } } @@ -248,6 +269,7 @@ impl World { self.humans .iter().map(|(id, x)| (AnyEntity::HumanID(id), (&x.trans, &x.it))), self.vehicles.iter().map(|(id, x)| (AnyEntity::VehicleID(id), (&x.trans, &x.it))), self.trains .iter().map(|(id, x)| (AnyEntity::TrainID(id), (&x.trans, &x.it))), + self.birds .iter().map(|(id, x)| (AnyEntity::BirdID(id), (&x.trans, &x.it))), )) } @@ -269,6 +291,7 @@ impl World { self.humans .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), self.trains .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), self.vehicles.values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), + self.birds .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), )) } @@ -433,6 +456,7 @@ impl Display for AnyEntity { AnyEntity::WagonID(id) => write!(f, "{:?}", id), AnyEntity::FreightStationID(id) => write!(f, "{:?}", id), AnyEntity::CompanyID(id) => write!(f, "{:?}", id), + AnyEntity::BirdID(id) => write!(f, "{:?}", id), } } } From 8269514492bf5d9f9fbbafb51d988f5c4f52d8f3 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Fri, 22 Dec 2023 15:35:33 -0600 Subject: [PATCH 02/12] negate house height --- simulation/src/transportation/bird_mob.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulation/src/transportation/bird_mob.rs b/simulation/src/transportation/bird_mob.rs index ef3d7e10..474a8fde 100644 --- a/simulation/src/transportation/bird_mob.rs +++ b/simulation/src/transportation/bird_mob.rs @@ -45,7 +45,7 @@ pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { let next_dests: Vec = map .buildings() .values() - .map(|building| building.door_pos.up(building.height)) + .map(|building| building.door_pos.up(-building.height)) // .chain( // map.terrain // .trees From 36bc4f8fed5da9e2e67300df1a317cf8baf121bf Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Fri, 22 Dec 2023 15:58:48 -0600 Subject: [PATCH 03/12] birds can wait at destination --- simulation/src/transportation/bird_mob.rs | 31 +++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/simulation/src/transportation/bird_mob.rs b/simulation/src/transportation/bird_mob.rs index 474a8fde..6088d881 100644 --- a/simulation/src/transportation/bird_mob.rs +++ b/simulation/src/transportation/bird_mob.rs @@ -59,6 +59,8 @@ pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { .for_each(|human| bird_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.bird_mob, &next_dests)) } +const BIRD_WAIT_TIME: f64 = 100.0; + pub fn bird_decision( time: &GameTime, it: &mut Itinerary, @@ -73,14 +75,27 @@ pub fn bird_decision( bird_mob.walk_anim %= 2.0 * std::f32::consts::PI; physics(kin, trans, time, desired_v, desired_dir); - if let Some(end_pos) = it.end_pos() { - if trans.position.is_close(end_pos, 3.0) { - // choose a random new destination - *it = Itinerary::simple(vec![ - next_dests[(next_dests.len() as f32 - * rand3(trans.position.x, trans.position.y, time.timestamp as f32)) - as usize], - ]); + let get_new_itinerary = || { + let n2 = rand3(trans.position.y, trans.position.x, time.timestamp as f32); + let n1 = rand3(trans.position.x, trans.position.y, time.timestamp as f32); + if n1 > 0.5 { + Itinerary::simple(vec![next_dests[(next_dests.len() as f32 * n2) as usize]]) + } else { + Itinerary::wait_until(time.timestamp + BIRD_WAIT_TIME) + } + }; + + // choose a random new destination if the current one has been reached + match it.end_pos() { + Some(end_pos) => { + if trans.position.is_close(end_pos, 3.0) { + *it = get_new_itinerary(); + } + } + None => { + if it.has_ended(time.timestamp) { + *it = get_new_itinerary(); + } } } } From decfdb7e1262545e5623d099ecac01c94f5ef142 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Fri, 22 Dec 2023 21:35:47 -0600 Subject: [PATCH 04/12] adjust bird size and flight animation --- assets/models/bird.glb | 4 ++-- native_app/src/rendering/entity_render.rs | 10 +++++++--- simulation/src/transportation/bird_mob.rs | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/assets/models/bird.glb b/assets/models/bird.glb index d7807ebe..ccaee1a3 100644 --- a/assets/models/bird.glb +++ b/assets/models/bird.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b9b7cc1785ff6313ceab5d71e51cba6768d124078b2414e4c6149b2ce9f4860 -size 44280 +oid sha256:763ebf93f5352727d382ff6ed724666b97fab98ef3d3f24f9c5aa75666d816b6 +size 44284 diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index 4b2db2fb..0e6fa745 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -95,10 +95,14 @@ impl InstancedRender { } } - for animal in sim.world().birds.values() { + for bird_ent in sim.world().birds.values() { self.birds.instances.push(MeshInstance { - pos: animal.trans.position, - dir: animal.trans.dir.xy().z0(), + pos: bird_ent.trans.position.up(if bird_ent.it.is_simple() { + 0.5 + 0.4 * bird_ent.bird_mob.fly_anim.cos() + } else { + 0.0 + }), + dir: bird_ent.trans.dir.xy().z0(), tint: LinearColor::WHITE, }); } diff --git a/simulation/src/transportation/bird_mob.rs b/simulation/src/transportation/bird_mob.rs index 6088d881..9928ef9f 100644 --- a/simulation/src/transportation/bird_mob.rs +++ b/simulation/src/transportation/bird_mob.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Inspect)] pub struct BirdMob { pub walking_speed: f32, - pub walk_anim: f32, + pub fly_anim: f32, } const PED_SIZE: f32 = 0.5; @@ -33,7 +33,7 @@ impl BirdMob { pub(crate) fn new(r: &mut RandProvider) -> Self { Self { walking_speed: (10.0 + r.next_f32() * 0.4), - walk_anim: 0.0, + fly_anim: 0.0, } } } @@ -71,8 +71,8 @@ pub fn bird_decision( ) { let (desired_v, desired_dir) = calc_decision(bird_mob, trans, it); - bird_mob.walk_anim += 7.0 * kin.0 * time.realdelta / bird_mob.walking_speed; - bird_mob.walk_anim %= 2.0 * std::f32::consts::PI; + bird_mob.fly_anim += 2.0 * kin.0 * time.realdelta / bird_mob.walking_speed; + bird_mob.fly_anim %= 2.0 * std::f32::consts::PI; physics(kin, trans, time, desired_v, desired_dir); let get_new_itinerary = || { From a22556677cbb35b3931a804e63da7a496f135003 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Sat, 23 Dec 2023 21:28:56 -0600 Subject: [PATCH 05/12] refactor bird module --- native_app/src/rendering/entity_render.rs | 9 +- simulation/src/init.rs | 2 +- simulation/src/transportation/bird_mob.rs | 137 ---------------------- simulation/src/transportation/mod.rs | 1 - simulation/src/wildlife/bird.rs | 113 +++++++++++++++++- 5 files changed, 117 insertions(+), 145 deletions(-) delete mode 100644 simulation/src/transportation/bird_mob.rs diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index 0e6fa745..4f36548c 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -97,11 +97,10 @@ impl InstancedRender { for bird_ent in sim.world().birds.values() { self.birds.instances.push(MeshInstance { - pos: bird_ent.trans.position.up(if bird_ent.it.is_simple() { - 0.5 + 0.4 * bird_ent.bird_mob.fly_anim.cos() - } else { - 0.0 - }), + pos: bird_ent + .trans + .position + .up(0.5 + 0.4 * bird_ent.bird_mob.fly_anim.cos()), dir: bird_ent.trans.dir.xy().z0(), tint: LinearColor::WHITE, }); diff --git a/simulation/src/init.rs b/simulation/src/init.rs index 1d3474d9..cd8bff2c 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -9,7 +9,6 @@ use crate::physics::coworld_synchronize; use crate::souls::freight_station::freight_station_system; use crate::souls::goods_company::{company_system, GoodsCompanyRegistry}; use crate::souls::human::update_decision_system; -use crate::transportation::bird_mob::bird_decision_system; use crate::transportation::pedestrian_decision_system; use crate::transportation::road::{vehicle_decision_system, vehicle_state_update_system}; use crate::transportation::testing_vehicles::{random_vehicles_update, RandomVehicles}; @@ -19,6 +18,7 @@ use crate::transportation::train::{ use crate::utils::resources::Resources; use crate::utils::time::Tick; use crate::wildlife::add_birds_randomly; +use crate::wildlife::bird::bird_decision_system; use crate::world::{CompanyEnt, FreightStationEnt, HumanEnt, TrainEnt, VehicleEnt, WagonEnt}; use crate::World; use crate::{ diff --git a/simulation/src/transportation/bird_mob.rs b/simulation/src/transportation/bird_mob.rs deleted file mode 100644 index 9928ef9f..00000000 --- a/simulation/src/transportation/bird_mob.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::map::Map; -use crate::map_dynamic::Itinerary; -use crate::physics::{Collider, CollisionWorld, PhysicsGroup, PhysicsObject, Speed}; -use crate::utils::rand_provider::RandProvider; -use crate::utils::resources::Resources; -use crate::utils::time::GameTime; -use crate::World; -use common::rand::rand3; -use egui_inspect::Inspect; -use geom::{angle_lerpxy, Transform, Vec3}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Inspect)] -pub struct BirdMob { - pub walking_speed: f32, - pub fly_anim: f32, -} - -const PED_SIZE: f32 = 0.5; - -pub fn put_bird_in_coworld(coworld: &mut CollisionWorld, pos: Vec3) -> Collider { - Collider(coworld.insert( - pos.xy(), - PhysicsObject { - radius: PED_SIZE * 0.6, - group: PhysicsGroup::Unknown, - ..Default::default() - }, - )) -} - -impl BirdMob { - pub(crate) fn new(r: &mut RandProvider) -> Self { - Self { - walking_speed: (10.0 + r.next_f32() * 0.4), - fly_anim: 0.0, - } - } -} - -pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { - profiling::scope!("transportation::animal_decision_system"); - let ra = &*resources.read(); - let map = &*resources.read::(); - let next_dests: Vec = map - .buildings() - .values() - .map(|building| building.door_pos.up(-building.height)) - // .chain( - // map.terrain - // .trees - // .objects() - // .map(|tree| vec3(tree.1.pos.x, tree.1.pos.y, tree.1.size)), - // ) - .collect(); - world.birds - .values_mut() - //.par_bridge() - .for_each(|human| bird_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.bird_mob, &next_dests)) -} - -const BIRD_WAIT_TIME: f64 = 100.0; - -pub fn bird_decision( - time: &GameTime, - it: &mut Itinerary, - trans: &mut Transform, - kin: &mut Speed, - bird_mob: &mut BirdMob, - next_dests: &Vec, -) { - let (desired_v, desired_dir) = calc_decision(bird_mob, trans, it); - - bird_mob.fly_anim += 2.0 * kin.0 * time.realdelta / bird_mob.walking_speed; - bird_mob.fly_anim %= 2.0 * std::f32::consts::PI; - physics(kin, trans, time, desired_v, desired_dir); - - let get_new_itinerary = || { - let n2 = rand3(trans.position.y, trans.position.x, time.timestamp as f32); - let n1 = rand3(trans.position.x, trans.position.y, time.timestamp as f32); - if n1 > 0.5 { - Itinerary::simple(vec![next_dests[(next_dests.len() as f32 * n2) as usize]]) - } else { - Itinerary::wait_until(time.timestamp + BIRD_WAIT_TIME) - } - }; - - // choose a random new destination if the current one has been reached - match it.end_pos() { - Some(end_pos) => { - if trans.position.is_close(end_pos, 3.0) { - *it = get_new_itinerary(); - } - } - None => { - if it.has_ended(time.timestamp) { - *it = get_new_itinerary(); - } - } - } -} - -const BIRD_ACC: f32 = 1.5; - -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; - } - const ANG_VEL: f32 = 1.0; - trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * time.realdelta); -} - -pub fn calc_decision(bird_mob: &mut BirdMob, trans: &Transform, it: &Itinerary) -> (f32, Vec3) { - 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.try_normalize() { - Some(x) => x, - None => return (0.0, trans.dir), - }; - - let desired_dir = dir_to_pos.normalize(); - (bird_mob.walking_speed, desired_dir) -} diff --git a/simulation/src/transportation/mod.rs b/simulation/src/transportation/mod.rs index 06ce7a67..e23d6aae 100644 --- a/simulation/src/transportation/mod.rs +++ b/simulation/src/transportation/mod.rs @@ -1,7 +1,6 @@ use crate::map::BuildingID; use serde::{Deserialize, Serialize}; -pub mod bird_mob; pub mod pedestrian; pub mod road; pub mod testing_vehicles; diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index 0bd5635a..9973c85f 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -1,10 +1,17 @@ +use crate::map::Map; use crate::map_dynamic::Itinerary; use crate::physics::Speed; -use crate::transportation::bird_mob::BirdMob; 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::rand3; +use egui_inspect::Inspect; +use geom::angle_lerpxy; use geom::{Transform, Vec3}; +use serde::{Deserialize, Serialize}; pub fn spawn_bird(sim: &mut Simulation, home_pos: Vec3) -> Option { profiling::scope!("spawn_bird"); @@ -26,3 +33,107 @@ pub fn spawn_bird(sim: &mut Simulation, home_pos: Vec3) -> Option { Some(id) } + +#[derive(Serialize, Deserialize, Inspect)] +pub struct BirdMob { + pub flying_speed: f32, + pub fly_anim: f32, +} + +impl BirdMob { + pub(crate) fn new(r: &mut RandProvider) -> Self { + Self { + flying_speed: (10.0 + r.next_f32() * 0.4), + fly_anim: 0.0, + } + } +} + +pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { + profiling::scope!("transportation::animal_decision_system"); + let ra = &*resources.read(); + let map = &*resources.read::(); + let next_dests: Vec = map + .buildings() + .values() + .map(|building| building.door_pos.up(-building.height)) + // .chain( + // map.terrain + // .trees + // .objects() + // .map(|tree| vec3(tree.1.pos.x, tree.1.pos.y, tree.1.size)), + // ) + .collect(); + world.birds + .values_mut() + //.par_bridge() + .for_each(|human| bird_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.bird_mob, &next_dests)) +} + +const BIRD_WAIT_TIME: f64 = 100.0; + +pub fn bird_decision( + time: &GameTime, + it: &mut Itinerary, + trans: &mut Transform, + kin: &mut Speed, + bird_mob: &mut BirdMob, + next_dests: &Vec, +) { + 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); + + let get_new_itinerary = || { + let n2 = rand3(trans.position.y, trans.position.x, time.timestamp as f32); + let n1 = rand3(trans.position.x, trans.position.y, time.timestamp as f32); + if n1 > 0.5 { + Itinerary::simple(vec![next_dests[(next_dests.len() as f32 * n2) as usize]]) + } else { + Itinerary::wait_until(time.timestamp + BIRD_WAIT_TIME) + } + }; + + // choose a random new destination if the current one has been reached + if it.has_ended(time.timestamp) { + *it = get_new_itinerary(); + } +} + +const BIRD_ACC: f32 = 1.5; + +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; + } + const ANG_VEL: f32 = 1.0; + trans.dir = angle_lerpxy(trans.dir, desired_dir, ANG_VEL * time.realdelta); +} + +pub fn calc_decision(bird_mob: &mut BirdMob, trans: &Transform, it: &Itinerary) -> (f32, Vec3) { + 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.try_normalize() { + Some(x) => x, + None => return (0.0, trans.dir), + }; + + let desired_dir = dir_to_pos.normalize(); + (bird_mob.flying_speed, desired_dir) +} From 873d1216e4df087e587c763f9561f3feaea59bf2 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Sat, 23 Dec 2023 21:32:29 -0600 Subject: [PATCH 06/12] fix crate error --- simulation/src/world.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulation/src/world.rs b/simulation/src/world.rs index 5e73d99b..70f44925 100644 --- a/simulation/src/world.rs +++ b/simulation/src/world.rs @@ -8,11 +8,11 @@ use crate::souls::desire::{BuyFood, Home, Work}; use crate::souls::freight_station::FreightStation; use crate::souls::goods_company::GoodsCompany; use crate::souls::human::{HumanDecision, PersonalInfo}; -use crate::transportation::bird_mob::BirdMob; use crate::transportation::train::{Locomotive, LocomotiveReservation, RailWagon}; use crate::transportation::{Location, Pedestrian, Vehicle, VehicleKind, VehicleState}; use crate::utils::par_command_buffer::SimDrop; use crate::utils::resources::Resources; +use crate::wildlife::bird::BirdMob; use crate::{impl_entity, impl_trans, SoulID}; use derive_more::{From, TryInto}; use geom::{Transform, Vec2, Vec3}; From dcc98b14f8155d0aaab10d0f13632c31978ca4e0 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Tue, 26 Dec 2023 21:50:25 -0600 Subject: [PATCH 07/12] add bird flock id --- native_app/src/rendering/entity_render.rs | 2 +- simulation/src/wildlife/bird.rs | 101 +++++++++++++++------- simulation/src/wildlife/mod.rs | 32 ++++--- 3 files changed, 95 insertions(+), 40 deletions(-) diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index 4f36548c..d5af5c34 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -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, "bird.glb").unwrap()), + birds: InstancedMeshBuilder::new(load_mesh(gfx, "rail_freight_station.glb").unwrap()), } } diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index 9973c85f..e9d3fd67 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -7,22 +7,23 @@ use crate::utils::time::GameTime; use crate::Simulation; use crate::World; use crate::{BirdEnt, BirdID}; -use common::rand::rand3; +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 { profiling::scope!("spawn_bird"); - log::info!("added bird at {}", home_pos); + // log::info!("added bird at {}", home_pos); - let a = BirdMob::new(&mut sim.write::()); + let mob = BirdMob::new(&mut sim.write::()); let id = sim.world.insert(BirdEnt { trans: Transform::new(home_pos), - bird_mob: a, + bird_mob: mob, it: Itinerary::simple(vec![Vec3 { x: home_pos.x, y: home_pos.y, @@ -38,47 +39,67 @@ pub fn spawn_bird(sim: &mut Simulation, home_pos: Vec3) -> Option { 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!("transportation::animal_decision_system"); - let ra = &*resources.read(); + profiling::scope!("wildlife::animal_decision_system"); + let ra = &*resources.read::(); let map = &*resources.read::(); - let next_dests: Vec = map - .buildings() - .values() - .map(|building| building.door_pos.up(-building.height)) - // .chain( - // map.terrain - // .trees - // .objects() - // .map(|tree| vec3(tree.1.pos.x, tree.1.pos.y, tree.1.size)), - // ) - .collect(); + + let aabb = map.terrain.bounds(); + let r = &mut RandProvider::new(ra.timestamp as u64); + world.birds .values_mut() //.par_bridge() - .for_each(|human| bird_decision(ra, &mut human.it, &mut human.trans, &mut human.speed, &mut human.bird_mob, &next_dests)) + .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 = 100000.0; +// likelihood the bird will stray from the flock itinerary +const DISTRACTED_PROB: f32 = 0.5; + pub fn bird_decision( time: &GameTime, it: &mut Itinerary, trans: &mut Transform, kin: &mut Speed, bird_mob: &mut BirdMob, - next_dests: &Vec, + aabb: AABB, + r: &mut RandProvider, ) { let (desired_v, desired_dir) = calc_decision(bird_mob, trans, it); @@ -86,19 +107,41 @@ pub fn bird_decision( bird_mob.fly_anim %= 2.0 * std::f32::consts::PI; physics(kin, trans, time, desired_v, desired_dir); - let get_new_itinerary = || { - let n2 = rand3(trans.position.y, trans.position.x, time.timestamp as f32); - let n1 = rand3(trans.position.x, trans.position.y, time.timestamp as f32); - if n1 > 0.5 { - Itinerary::simple(vec![next_dests[(next_dests.len() as f32 * n2) as usize]]) - } else { - Itinerary::wait_until(time.timestamp + BIRD_WAIT_TIME) - } - }; + // a random nearby location to hang around + let random_itinerary = Itinerary::simple(vec![get_random_pos_from_center( + trans.position, + 5.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) { - *it = get_new_itinerary(); + 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); + } } } diff --git a/simulation/src/wildlife/mod.rs b/simulation/src/wildlife/mod.rs index 1bab5c24..fb0dd762 100644 --- a/simulation/src/wildlife/mod.rs +++ b/simulation/src/wildlife/mod.rs @@ -1,24 +1,36 @@ -use crate::wildlife::bird::spawn_bird; +use crate::utils::rand_provider::RandProvider; +use crate::wildlife::bird::{ + get_random_bird_pos, get_random_pos_from_center, spawn_bird, NUM_FLOCKS, +}; use crate::Simulation; -use common::rand::{rand, rand2}; -use geom::Vec3; +use common::rand::{rand, rand2, rand3}; pub mod bird; -/// HACK (for now): add no more than 50 birds to random locations +const NUM_BIRDS: u32 = 1000; + +/// HACK (for now): spawns birds in random clusters around the map pub(crate) fn add_birds_randomly(sim: &mut Simulation) { profiling::scope!("wildlife::add_birds_randomly"); let num_birds = sim.world().birds.len(); - if num_birds > 50 { + if num_birds > NUM_BIRDS as usize { return; } - let home_pos = Vec3 { - x: 5000.0 + 1000.0 * rand(num_birds as f32), - y: 5000.0 + 1000.0 * rand2(num_birds as f32, num_birds as f32), - z: 0.0, - }; + let aabb = sim.map().terrain.bounds(); + let seed = (num_birds as u32 / (NUM_BIRDS / NUM_FLOCKS)) as f32; + let cluster_pos = + get_random_bird_pos(aabb, rand(seed), rand2(seed, seed), rand3(seed, seed, seed)); + + let mut rng = RandProvider::new(num_birds as u64); + let home_pos = get_random_pos_from_center( + cluster_pos, + 5.0, + rng.next_f32(), + rng.next_f32(), + rng.next_f32(), + ); spawn_bird(sim, home_pos); } From dffd9bb721c4f21106f225a03faceab7b89eacff Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Thu, 28 Dec 2023 22:49:14 -0600 Subject: [PATCH 08/12] add flock entity with naive flock behavior --- native_app/src/gui/selectable.rs | 3 +- simulation/src/init.rs | 4 +-- simulation/src/wildlife/bird.rs | 60 +++++++++++++++++++++++++++----- simulation/src/wildlife/mod.rs | 49 +++++++++++++++----------- simulation/src/world.rs | 9 +++++ 5 files changed, 93 insertions(+), 32 deletions(-) diff --git a/native_app/src/gui/selectable.rs b/native_app/src/gui/selectable.rs index 4305b2f9..97753e77 100644 --- a/native_app/src/gui/selectable.rs +++ b/native_app/src/gui/selectable.rs @@ -13,7 +13,8 @@ pub fn select_radius(id: AnyEntity) -> f32 { AnyEntity::FreightStationID(_) => 0.0, AnyEntity::CompanyID(_) => 0.0, AnyEntity::HumanID(_) => 3.0, - AnyEntity::BirdID(_) => 3.0, + // TODO: make the radius smaller after finishing testing + AnyEntity::BirdID(_) => 20.0, } } diff --git a/simulation/src/init.rs b/simulation/src/init.rs index cd8bff2c..5c600a1b 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -17,7 +17,7 @@ use crate::transportation::train::{ }; use crate::utils::resources::Resources; use crate::utils::time::Tick; -use crate::wildlife::add_birds_randomly; +use crate::wildlife::add_flocks_randomly; use crate::wildlife::bird::bird_decision_system; use crate::world::{CompanyEnt, FreightStationEnt, HumanEnt, TrainEnt, VehicleEnt, WagonEnt}; use crate::World; @@ -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_birds_randomly); + register_system_sim("add_birds_randomly", add_flocks_randomly); register_resource_noserialize::(); register_resource_noserialize::(); diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index e9d3fd67..0f7ce165 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -62,10 +62,38 @@ pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { let aabb = map.terrain.bounds(); let r = &mut RandProvider::new(ra.timestamp as u64); - 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)) + world.flocks.values().for_each(|flock| { + let flock_physics: Vec<(Transform, Speed)> = flock + .bird_ids + .iter() + .map(|bird_id| match world.birds.get_mut(*bird_id) { + Some(bird_ent) => (bird_ent.trans, bird_ent.speed.clone()), + None => unreachable!(), + }) + .collect(); + + 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, + 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; @@ -88,7 +116,7 @@ pub fn get_random_pos_from_center(center: Vec3, radius: f32, r1: f32, r2: f32, r } // amount of time per each flock itinerary -const FLOCK_IT_PERIOD: f32 = 100000.0; +const FLOCK_IT_PERIOD: f32 = 20000.0; // likelihood the bird will stray from the flock itinerary const DISTRACTED_PROB: f32 = 0.5; @@ -100,6 +128,7 @@ pub fn bird_decision( bird_mob: &mut BirdMob, aabb: AABB, r: &mut RandProvider, + flock_physics: &Vec<(Transform, Speed)>, ) { let (desired_v, desired_dir) = calc_decision(bird_mob, trans, it); @@ -110,7 +139,7 @@ pub fn bird_decision( // a random nearby location to hang around let random_itinerary = Itinerary::simple(vec![get_random_pos_from_center( trans.position, - 5.0, + 100.0, r.next_f32(), r.next_f32(), r.next_f32(), @@ -160,10 +189,19 @@ pub fn physics( kin.0 += mag; } 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); } 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), @@ -172,11 +210,17 @@ pub fn calc_decision(bird_mob: &mut BirdMob, trans: &Transform, it: &Itinerary) let position = trans.position; let delta_pos: Vec3 = objective - position; - let dir_to_pos = match delta_pos.try_normalize() { - Some(x) => x, + 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) } diff --git a/simulation/src/wildlife/mod.rs b/simulation/src/wildlife/mod.rs index fb0dd762..c2d52ea1 100644 --- a/simulation/src/wildlife/mod.rs +++ b/simulation/src/wildlife/mod.rs @@ -2,35 +2,42 @@ use crate::utils::rand_provider::RandProvider; use crate::wildlife::bird::{ get_random_bird_pos, get_random_pos_from_center, spawn_bird, NUM_FLOCKS, }; -use crate::Simulation; -use common::rand::{rand, rand2, rand3}; +use crate::{BirdID, Flock, Simulation}; pub mod bird; -const NUM_BIRDS: u32 = 1000; +const BIRDS_PER_FLOCK: u32 = 50; +const DIST_SPREAD: f32 = 5.0; // how large of a radius to initially spawn birds /// HACK (for now): spawns birds in random clusters around the map -pub(crate) fn add_birds_randomly(sim: &mut Simulation) { - profiling::scope!("wildlife::add_birds_randomly"); +pub(crate) fn add_flocks_randomly(sim: &mut Simulation) { + profiling::scope!("wildlife::add_flocks_randomly"); - let num_birds = sim.world().birds.len(); - if num_birds > NUM_BIRDS as usize { + let num_flocks = sim.world().flocks.len(); + if num_flocks >= NUM_FLOCKS as usize { return; } + let mut rng = RandProvider::new(num_flocks as u64); + let aabb = sim.map().terrain.bounds(); - let seed = (num_birds as u32 / (NUM_BIRDS / NUM_FLOCKS)) as f32; - let cluster_pos = - get_random_bird_pos(aabb, rand(seed), rand2(seed, seed), rand3(seed, seed, seed)); - - let mut rng = RandProvider::new(num_birds as u64); - - let home_pos = get_random_pos_from_center( - cluster_pos, - 5.0, - rng.next_f32(), - rng.next_f32(), - rng.next_f32(), - ); - spawn_bird(sim, home_pos); + let center_pos = get_random_bird_pos(aabb, rng.next_f32(), rng.next_f32(), rng.next_f32()); + + let mut ids: Vec = Vec::new(); + + for _ in 0..BIRDS_PER_FLOCK { + let bird_pos = get_random_pos_from_center( + center_pos, + DIST_SPREAD, + rng.next_f32(), + rng.next_f32(), + rng.next_f32(), + ); + match spawn_bird(sim, bird_pos) { + Some(id) => ids.push(id), + None => (), + } + } + + sim.world.insert(Flock { bird_ids: ids }); } diff --git a/simulation/src/world.rs b/simulation/src/world.rs index 70f44925..2374b237 100644 --- a/simulation/src/world.rs +++ b/simulation/src/world.rs @@ -29,6 +29,7 @@ new_key_type! { pub struct FreightStationID; pub struct CompanyID; pub struct BirdID; + pub struct FlockID; } impl_entity!(VehicleID, VehicleEnt, vehicles); @@ -38,6 +39,7 @@ impl_entity!(WagonID, WagonEnt, wagons); impl_entity!(FreightStationID, FreightStationEnt, freight_stations); impl_entity!(CompanyID, CompanyEnt, companies); impl_entity!(BirdID, BirdEnt, birds); +impl_entity!(FlockID, Flock, flocks); impl_trans!(HumanID); impl_trans!(VehicleID); @@ -194,6 +196,11 @@ impl SimDrop for BirdEnt { fn sim_drop(self, _: BirdID, _: &mut Resources) {} } +#[derive(Serialize, Deserialize)] +pub struct Flock { + pub bird_ids: Vec, +} + #[derive(Default, Serialize, Deserialize)] pub struct World { pub vehicles: HopSlotMap, @@ -203,6 +210,7 @@ pub struct World { pub freight_stations: HopSlotMap, pub companies: HopSlotMap, pub birds: HopSlotMap, + pub flocks: HopSlotMap, } impl World { @@ -280,6 +288,7 @@ impl World { self.vehicles.iter().map(|(id, x)| (AnyEntity::VehicleID(id), x.trans.position.xy())), self.trains .iter().map(|(id, x)| (AnyEntity::TrainID(id), x.trans.position.xy())), self.wagons .iter().map(|(id, x)| (AnyEntity::WagonID(id), x.trans.position.xy())), + self.birds .iter().map(|(id, x)| (AnyEntity::BirdID(id), x.trans.position.xy())), )) } From 39c6e81e0f734d8db268c3037f3ce8130edeb14b Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Fri, 29 Dec 2023 14:47:17 -0600 Subject: [PATCH 09/12] implement boids --- native_app/src/rendering/entity_render.rs | 9 +- simulation/src/init.rs | 2 +- simulation/src/wildlife/bird.rs | 252 ++++++++-------------- simulation/src/wildlife/mod.rs | 35 ++- simulation/src/world.rs | 6 - 5 files changed, 127 insertions(+), 177 deletions(-) diff --git a/native_app/src/rendering/entity_render.rs b/native_app/src/rendering/entity_render.rs index d5af5c34..ce74c524 100644 --- a/native_app/src/rendering/entity_render.rs +++ b/native_app/src/rendering/entity_render.rs @@ -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()), } } @@ -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, }); } diff --git a/simulation/src/init.rs b/simulation/src/init.rs index 5c600a1b..39f81ef5 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -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::(); register_resource_noserialize::(); diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index 0f7ce165..268c2674 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -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 { +pub fn spawn_bird(sim: &mut Simulation, spawn_pos: Vec3) -> Option { profiling::scope!("spawn_bird"); - // log::info!("added bird at {}", home_pos); - - let mob = BirdMob::new(&mut sim.write::()); + 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::(); let map = &*resources.read::(); let aabb = map.terrain.bounds(); - let r = &mut RandProvider::new(ra.timestamp as u64); world.flocks.values().for_each(|flock| { let flock_physics: Vec<(Transform, Speed)> = flock @@ -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 } diff --git a/simulation/src/wildlife/mod.rs b/simulation/src/wildlife/mod.rs index c2d52ea1..f79b0dd2 100644 --- a/simulation/src/wildlife/mod.rs +++ b/simulation/src/wildlife/mod.rs @@ -1,13 +1,36 @@ +use geom::{Vec3, AABB}; + use crate::utils::rand_provider::RandProvider; -use crate::wildlife::bird::{ - get_random_bird_pos, get_random_pos_from_center, spawn_bird, NUM_FLOCKS, -}; +use crate::wildlife::bird::spawn_bird; use crate::{BirdID, Flock, Simulation}; pub mod bird; +const MIN_SPAWN_HEIGHT: f32 = 2.0; +const SPAWN_HEIGHT_RANGE: f32 = 10.0; + +/// Get a random position within the bounding box +pub fn get_random_spawn_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: MIN_SPAWN_HEIGHT + SPAWN_HEIGHT_RANGE * r3, + } +} + +/// Get a random position within the ball with the given center and radius +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: MIN_SPAWN_HEIGHT + SPAWN_HEIGHT_RANGE * r3, + } +} + +const NUM_FLOCKS: u32 = 20; const BIRDS_PER_FLOCK: u32 = 50; -const DIST_SPREAD: f32 = 5.0; // how large of a radius to initially spawn birds +const SPAWN_RANGE: f32 = 5.0; // how spread out birds in the flock should be initially /// HACK (for now): spawns birds in random clusters around the map pub(crate) fn add_flocks_randomly(sim: &mut Simulation) { @@ -21,14 +44,14 @@ pub(crate) fn add_flocks_randomly(sim: &mut Simulation) { let mut rng = RandProvider::new(num_flocks as u64); let aabb = sim.map().terrain.bounds(); - let center_pos = get_random_bird_pos(aabb, rng.next_f32(), rng.next_f32(), rng.next_f32()); + let center_pos = get_random_spawn_pos(aabb, rng.next_f32(), rng.next_f32(), rng.next_f32()); let mut ids: Vec = Vec::new(); for _ in 0..BIRDS_PER_FLOCK { let bird_pos = get_random_pos_from_center( center_pos, - DIST_SPREAD, + SPAWN_RANGE, rng.next_f32(), rng.next_f32(), rng.next_f32(), diff --git a/simulation/src/world.rs b/simulation/src/world.rs index 2374b237..337a548d 100644 --- a/simulation/src/world.rs +++ b/simulation/src/world.rs @@ -12,7 +12,6 @@ use crate::transportation::train::{Locomotive, LocomotiveReservation, RailWagon} use crate::transportation::{Location, Pedestrian, Vehicle, VehicleKind, VehicleState}; use crate::utils::par_command_buffer::SimDrop; use crate::utils::resources::Resources; -use crate::wildlife::bird::BirdMob; use crate::{impl_entity, impl_trans, SoulID}; use derive_more::{From, TryInto}; use geom::{Transform, Vec2, Vec3}; @@ -188,8 +187,6 @@ impl SimDrop for CompanyEnt { pub struct BirdEnt { pub trans: Transform, pub speed: Speed, - pub bird_mob: BirdMob, - pub it: Itinerary, } impl SimDrop for BirdEnt { @@ -258,7 +255,6 @@ impl World { AnyEntity::VehicleID(x) => Some(&self.get(x)?.it), AnyEntity::TrainID(x) => Some(&self.get(x)?.it), AnyEntity::HumanID(x) => Some(&self.get(x)?.it), - AnyEntity::BirdID(x) => Some(&self.get(x)?.it), _ => None, } } @@ -277,7 +273,6 @@ impl World { self.humans .iter().map(|(id, x)| (AnyEntity::HumanID(id), (&x.trans, &x.it))), self.vehicles.iter().map(|(id, x)| (AnyEntity::VehicleID(id), (&x.trans, &x.it))), self.trains .iter().map(|(id, x)| (AnyEntity::TrainID(id), (&x.trans, &x.it))), - self.birds .iter().map(|(id, x)| (AnyEntity::BirdID(id), (&x.trans, &x.it))), )) } @@ -300,7 +295,6 @@ impl World { self.humans .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), self.trains .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), self.vehicles.values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), - self.birds .values_mut().map(|h| (&mut h.it, &mut h.trans, h.speed.0)), )) } From 62a8ea1b460d4b894235d5bfb53aaf01c3748256 Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Fri, 29 Dec 2023 18:40:53 -0600 Subject: [PATCH 10/12] change map.terrain to map.environment --- simulation/src/wildlife/bird.rs | 2 +- simulation/src/wildlife/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index 268c2674..75a7a59b 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -27,7 +27,7 @@ pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { let ra = &*resources.read::(); let map = &*resources.read::(); - let aabb = map.terrain.bounds(); + let aabb = map.environment.bounds(); world.flocks.values().for_each(|flock| { let flock_physics: Vec<(Transform, Speed)> = flock diff --git a/simulation/src/wildlife/mod.rs b/simulation/src/wildlife/mod.rs index f79b0dd2..b83759c7 100644 --- a/simulation/src/wildlife/mod.rs +++ b/simulation/src/wildlife/mod.rs @@ -43,7 +43,7 @@ pub(crate) fn add_flocks_randomly(sim: &mut Simulation) { let mut rng = RandProvider::new(num_flocks as u64); - let aabb = sim.map().terrain.bounds(); + let aabb = sim.map().environment.bounds(); let center_pos = get_random_spawn_pos(aabb, rng.next_f32(), rng.next_f32(), rng.next_f32()); let mut ids: Vec = Vec::new(); From c12ea0a775a2499c66b7b143fd8b9af0b94a766f Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Mon, 1 Jan 2024 12:52:10 -0600 Subject: [PATCH 11/12] adjust boids parameters --- simulation/src/wildlife/bird.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index 75a7a59b..b3107098 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -73,16 +73,18 @@ pub fn bird_decision( // the initial velocity of the bird let mut dv = trans.dir * kin.0; - // fly towards the flock center - const CENTERING_FACTOR: f32 = 0.1; - dv += (flock_center - trans.position) * CENTERING_FACTOR; + // fly towards the average position of all other birds + const CENTERING_FACTOR: f32 = 0.01; + let num_birds = flock_physics.len() as f32; + let perceived_center = (flock_center * num_birds - trans.position) / (num_birds - 1.0); + dv += (perceived_center - trans.position) * CENTERING_FACTOR; // match the flock's average velocity - const MATCHING_FACTOR: f32 = 0.1; + const MATCHING_FACTOR: f32 = 0.01; dv += (flock_avg_v - dv) * MATCHING_FACTOR; // avoid nearby birds - const AVOID_FACTOR: f32 = 0.1; + const AVOID_FACTOR: f32 = 0.01; dv += separation_adjustment(trans, flock_physics) * AVOID_FACTOR; // avoid map boundaries @@ -123,11 +125,11 @@ fn average_velocity(flock_physics: &Vec<(Transform, Speed)>) -> Vec3 { /// 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; + const MIN_DISTANCE: f32 = 5.0; flock_physics .iter() - .filter(|(t, _)| t.position.distance(trans.position) < MIN_DISTANCE) - .map(|(t, _)| trans.position - t.position) + .filter(|(other, _)| other.position.distance(trans.position) < MIN_DISTANCE) + .map(|(other, _)| trans.position - other.position) // TODO: use .sum() ? .reduce(|a, b| a + b) .unwrap() From 6d06c069a765f0d6d870335a70a34cb7dcb0de8b Mon Sep 17 00:00:00 2001 From: Reece Yang Date: Mon, 1 Jan 2024 13:05:35 -0600 Subject: [PATCH 12/12] update some names and comments --- simulation/src/wildlife/bird.rs | 8 +++++--- simulation/src/wildlife/mod.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/simulation/src/wildlife/bird.rs b/simulation/src/wildlife/bird.rs index b3107098..878e71fa 100644 --- a/simulation/src/wildlife/bird.rs +++ b/simulation/src/wildlife/bird.rs @@ -9,6 +9,7 @@ use geom::angle_lerpxy; use geom::AABB; use geom::{Transform, Vec3}; +/// spawns a bird in the world pub fn spawn_bird(sim: &mut Simulation, spawn_pos: Vec3) -> Option { profiling::scope!("spawn_bird"); @@ -22,8 +23,9 @@ pub fn spawn_bird(sim: &mut Simulation, spawn_pos: Vec3) -> Option { Some(id) } +/// Update the movement of each bird in the world pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { - profiling::scope!("wildlife::animal_decision_system"); + profiling::scope!("wildlife::bird_decision_system"); let ra = &*resources.read::(); let map = &*resources.read::(); @@ -60,7 +62,7 @@ pub fn bird_decision_system(world: &mut World, resources: &mut Resources) { }); } -/// Update the speed, position, and direction of a bird +/// Update the speed, position, and direction of a bird using the boids algorithm pub fn bird_decision( time: &GameTime, trans: &mut Transform, @@ -141,7 +143,7 @@ fn bounds_adjustment(trans: &Transform, aabb: AABB) -> Vec3 { 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 + // TODO: the ground might not be at z: 0 if trans.position.z < MARGIN { v.z += TURN_AMOUNT; } diff --git a/simulation/src/wildlife/mod.rs b/simulation/src/wildlife/mod.rs index b83759c7..67725f34 100644 --- a/simulation/src/wildlife/mod.rs +++ b/simulation/src/wildlife/mod.rs @@ -32,7 +32,7 @@ const NUM_FLOCKS: u32 = 20; const BIRDS_PER_FLOCK: u32 = 50; const SPAWN_RANGE: f32 = 5.0; // how spread out birds in the flock should be initially -/// HACK (for now): spawns birds in random clusters around the map +/// spawns birds in random clusters around the map pub(crate) fn add_flocks_randomly(sim: &mut Simulation) { profiling::scope!("wildlife::add_flocks_randomly");