diff --git a/resources/prefabs/creatures/carnivore.ron b/resources/prefabs/creatures/carnivore.ron index d6973e7..03c41a8 100644 --- a/resources/prefabs/creatures/carnivore.ron +++ b/resources/prefabs/creatures/carnivore.ron @@ -50,9 +50,14 @@ Prefab ( faction: "Carnivores", ), ), - intelligence_tag: (), perception: ( - range: 5.0, + range: 6.0, + ), + boids: ( + seek: ( + names: ["Herbivore"], + strength: 10.0, + ), ), ), ), diff --git a/resources/prefabs/creatures/herbivore.ron b/resources/prefabs/creatures/herbivore.ron index 84cc7ac..873bb9b 100644 --- a/resources/prefabs/creatures/herbivore.ron +++ b/resources/prefabs/creatures/herbivore.ron @@ -50,9 +50,28 @@ Prefab ( faction: "Herbivores", ), ), - intelligence_tag: (), perception: ( - range: 5.0, + range: 4.0, + ), + boids: ( + flocking: ( + strength: 0.4, + ), + match_velocity: ( + strength: 3.0, + ), + avoid: ( + names: ["Carnivore"], + strength: 20.0, + ), + seek: ( + names: ["Plant"], + strength: 10.0, + ), + minimum_distance: ( + minimum: 1.5, + strength: 5.0, + ), ), ), ), diff --git a/src/components/creatures.rs b/src/components/creatures.rs index ef734a6..d102fff 100644 --- a/src/components/creatures.rs +++ b/src/components/creatures.rs @@ -10,8 +10,8 @@ use amethyst_inspector::Inspect; use serde::{Deserialize, Serialize}; use crate::components::{ - collider::Circle, combat::CombatPrefabData, digestion::DigestionPrefabData, - perception::Perception, + boids::BoidsPrefabData, collider::Circle, combat::CombatPrefabData, + digestion::DigestionPrefabData, perception::Perception, }; pub type CreatureType = String; @@ -84,4 +84,5 @@ pub struct CreaturePrefabData { combat: Option, intelligence_tag: Option, perception: Option, + boids: Option, } diff --git a/src/components/experimental/boids.rs b/src/components/experimental/boids.rs new file mode 100644 index 0000000..437e29d --- /dev/null +++ b/src/components/experimental/boids.rs @@ -0,0 +1,96 @@ +use amethyst::{ + assets::{PrefabData, PrefabError, ProgressCounter}, + derive::PrefabData, + ecs::{Component, DenseVecStorage, Entity, WriteStorage}, +}; +use amethyst_inspector::Inspect; + +use serde::{Deserialize, Serialize}; + +#[derive(Default, Clone, Inspect, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +#[serde(default)] +pub struct FlockingRule { + pub strength: f32, +} + +impl Component for FlockingRule { + type Storage = DenseVecStorage; +} + +#[derive(Default, Clone, Inspect, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +#[serde(default)] +pub struct MatchVelocityRule { + pub strength: f32, +} + +impl Component for MatchVelocityRule { + type Storage = DenseVecStorage; +} + +#[derive(Default, Clone, Inspect, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +#[serde(default)] +pub struct MinimumDistanceRule { + pub minimum: f32, + pub strength: f32, +} + +impl Component for MinimumDistanceRule { + type Storage = DenseVecStorage; +} + +#[derive(Default, Clone, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +#[serde(default)] +pub struct AvoidRule { + pub names: Vec, + pub strength: f32, +} + +impl Component for AvoidRule { + type Storage = DenseVecStorage; +} + +#[derive(Default, Clone, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +#[serde(default)] +pub struct SeekRule { + pub names: Vec, + pub strength: f32, +} + +impl Component for SeekRule { + type Storage = DenseVecStorage; +} + +#[derive(Clone, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +pub struct WorldBoundsRule { + pub strength: f32, +} + +impl Default for WorldBoundsRule { + fn default() -> Self { + WorldBoundsRule { strength: 20.0 } + } +} + +impl Component for WorldBoundsRule { + type Storage = DenseVecStorage; +} + +#[derive(Default, Deserialize, Serialize, PrefabData)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct BoidsPrefabData { + flocking: Option, + match_velocity: Option, + avoid: Option, + seek: Option, + minimum_distance: Option, + + #[serde(skip)] + wall_bounds: WorldBoundsRule, +} diff --git a/src/components/experimental/mod.rs b/src/components/experimental/mod.rs index d9db8c2..8e7677c 100644 --- a/src/components/experimental/mod.rs +++ b/src/components/experimental/mod.rs @@ -1 +1,2 @@ +pub mod boids; pub mod perception; diff --git a/src/components/experimental/perception.rs b/src/components/experimental/perception.rs index f8c92bb..9d30a51 100644 --- a/src/components/experimental/perception.rs +++ b/src/components/experimental/perception.rs @@ -1,6 +1,6 @@ use amethyst::{ assets::{PrefabData, PrefabError}, - ecs::{Component, DenseVecStorage, Entity, WriteStorage}, + ecs::{BitSet, Component, DenseVecStorage, Entity, WriteStorage}, }; use amethyst_inspector::Inspect; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ impl Component for Perception { #[derive(Default, Clone, Debug)] pub struct DetectedEntities { - pub entities: Vec, + pub entities: BitSet, } impl Component for DetectedEntities { diff --git a/src/resources/experimental/spatial_grid.rs b/src/resources/experimental/spatial_grid.rs index 0306b9a..e4d68e2 100644 --- a/src/resources/experimental/spatial_grid.rs +++ b/src/resources/experimental/spatial_grid.rs @@ -1,6 +1,6 @@ use amethyst::{ core::{nalgebra::Vector4, transform::GlobalTransform}, - ecs::Entity, + ecs::{BitSet, Entity}, }; use std::collections::HashMap; @@ -9,7 +9,7 @@ use std::f32; // The SpatialGrid is a spatial hashing structure used to accelerate neighbor searches for entities. pub struct SpatialGrid { cell_size: f32, - cells: HashMap>>, + cells: HashMap>, } impl SpatialGrid { @@ -31,23 +31,23 @@ impl SpatialGrid { let x_cell = (pos[0] / self.cell_size).floor() as i32; let y_cell = (pos[1] / self.cell_size).floor() as i32; let row_entry = self.cells.entry(x_cell).or_insert(HashMap::new()); - let col_entry = row_entry.entry(y_cell).or_insert(Vec::new()); - col_entry.push(entity); + let col_entry = row_entry.entry(y_cell).or_insert(BitSet::new()); + col_entry.add(entity.id()); } // Query the entities close to a certain position. // The range of the query is defined by the range input. - pub fn query(&self, transform: &GlobalTransform, range: f32) -> Vec { + pub fn query(&self, transform: &GlobalTransform, range: f32) -> BitSet { let pos = Vector4::from(transform.as_ref()[3]); let x_cell = (pos[0] / self.cell_size).floor() as i32; let y_cell = (pos[1] / self.cell_size).floor() as i32; let integer_range = 1 + (range / self.cell_size).ceil() as i32; - let mut entities = Vec::new(); + let mut entities = BitSet::new(); for x in -integer_range..integer_range { for y in -integer_range..integer_range { match self.cells.get(&(x_cell + x)) { Some(col) => match col.get(&(y_cell + y)) { - Some(cell) => entities.extend_from_slice(cell.as_slice()), + Some(cell) => entities |= cell, None => (), }, None => (), @@ -76,7 +76,13 @@ mod tests { let global_transform = GlobalTransform::from(*transform_matrix.as_ref()); spatial_grid.insert(world.create_entity().build(), &global_transform); - assert!(spatial_grid.query(&global_transform, 1.0f32).len() == 1); + assert!( + spatial_grid + .query(&global_transform, 1.0f32) + .into_iter() + .count() + == 1 + ); } } diff --git a/src/states/main_game.rs b/src/states/main_game.rs index fdc6c9e..ee91c4c 100644 --- a/src/states/main_game.rs +++ b/src/states/main_game.rs @@ -43,76 +43,86 @@ impl MainGameState { "entity_detection", &["spatial_grid"], ) + .with(boids::FlockingSystem, "flocking", &["entity_detection"]) .with( - QueryPredatorsAndPreySystem, - "query_predators_and_prey_system", - &[], - ) - .with(ClosestObstacleSystem, "closest_obstacle_system", &[]) - .with( - ClosestSystem::::default(), - "closest_prey_system", - &["query_predators_and_prey_system"], - ) - .with( - ClosestSystem::::default(), - "closest_predator_system", - &["query_predators_and_prey_system"], - ) - .with( - SeekSystem::::new( - Rotation3::from_axis_angle(&Vector3::z_axis(), 0.0), - 1.0, - ), - "seek_prey_system", - &["closest_prey_system"], + boids::MatchVelocitySystem, + "match_velocity", + &["entity_detection"], ) + .with(boids::AvoidSystem, "avoid", &["entity_detection"]) + .with(boids::SeekSystem, "seek", &["entity_detection"]) .with( - SeekSystem::::new( - // 180 degrees, run away! - Rotation3::from_axis_angle(&Vector3::z_axis(), std::f32::consts::PI), - 1.0, - ), - "avoid_predator_system", - &["closest_predator_system"], + boids::MinimumDistanceSystem, + "minimum_distance", + &["entity_detection"], ) .with( - SeekSystem::::new( - // 120 degrees. A little more than perpendicular so the creature - // tries to steer away from the wall rather than just follow it. - Rotation3::from_axis_angle( - &Vector3::z_axis(), - 2f32 * std::f32::consts::FRAC_PI_3, - ), - 5.0, - ), - "avoid_obstacle_system", - &["closest_obstacle_system"], - ) - .with( - behaviors::wander::WanderSystem, - "wander_system", - &[ - "seek_prey_system", - "avoid_predator_system", - "avoid_obstacle_system", - ], - ) - .with( - movement::MovementSystem, - "movement_system", - &["wander_system"], + boids::WorldBoundsSystem, + "world_bounds", + &["entity_detection"], ) + //.with( + //QueryPredatorsAndPreySystem, + //"query_predators_and_prey_system", + //&[], + //) + //.with(ClosestObstacleSystem, "closest_obstacle_system", &[]) + //.with( + //ClosestSystem::::default(), + //"closest_prey_system", + //&["query_predators_and_prey_system"], + //) + //.with( + //ClosestSystem::::default(), + //"closest_predator_system", + //&["query_predators_and_prey_system"], + //) + //.with( + //SeekSystem::::new( + //Rotation3::from_axis_angle(&Vector3::z_axis(), 0.0), + //1.0, + //), + //"seek_prey_system", + //&["closest_prey_system"], + //) + //.with( + //SeekSystem::::new( + //// 180 degrees, run away! + //Rotation3::from_axis_angle(&Vector3::z_axis(), std::f32::consts::PI), + //1.0, + //), + //"avoid_predator_system", + //&["closest_predator_system"], + //) + //.with( + //SeekSystem::::new( + //// 120 degrees. A little more than perpendicular so the creature + //// tries to steer away from the wall rather than just follow it. + //Rotation3::from_axis_angle( + //&Vector3::z_axis(), + //2f32 * std::f32::consts::FRAC_PI_3, + //), + //5.0, + //), + //"avoid_obstacle_system", + //&["closest_obstacle_system"], + //) + //.with( + //behaviors::wander::WanderSystem, + //"wander_system", + //&["seek_prey_system", "avoid_predator_system"], + //) + .with(movement::MovementSystem, "movement_system", &[]) .with( collision::CollisionSystem, "collision_system", &["movement_system"], ) - .with( - collision::EnforceBoundsSystem, - "enforce_bounds_system", - &["movement_system"], - ) + //.with( + //collision::EnforceBoundsSystem, + //"enforce_bounds_system", + //&["movement_system"], + //) .with(digestion::DigestionSystem, "digestion_system", &[]) .with( digestion::StarvationSystem, @@ -275,7 +285,7 @@ impl<'a> State, CustomStateEvent> for MainGameState { }; let mut transform = Transform::default(); - transform.set_position([0.0, 0.0, 12.0].into()); + transform.set_position([0.0, 0.0, 20.0].into()); data.world .create_entity() diff --git a/src/systems/behaviors/wander.rs b/src/systems/behaviors/wander.rs index bb68b6d..bddce6a 100644 --- a/src/systems/behaviors/wander.rs +++ b/src/systems/behaviors/wander.rs @@ -1,57 +1,57 @@ -use amethyst::core::transform::Transform; -use amethyst::core::Time; -use amethyst::ecs::*; -use amethyst::renderer::*; - -use crate::components::creatures; -use rand::{thread_rng, Rng}; - -pub struct WanderSystem; -impl<'s> System<'s> for WanderSystem { - type SystemData = ( - WriteStorage<'s, creatures::Wander>, - WriteStorage<'s, creatures::Movement>, - ReadStorage<'s, Transform>, - Write<'s, DebugLines>, - Read<'s, Time>, - ); - - fn run( - &mut self, - (mut wanders, mut movements, locals, mut debug_lines, time): Self::SystemData, - ) { - let delta_time = time.delta_seconds(); - let mut rng = thread_rng(); - - for (wander, movement, local) in (&mut wanders, &mut movements, &locals).join() { - let position = local.translation(); - let future_position = position + movement.velocity * 0.5; - - let direction = wander.get_direction(); - let target = future_position + direction; - - let desired_velocity = target - position; - - movement.velocity += desired_velocity * delta_time; - - let change = 10.0; - if rng.gen::() { - wander.angle += change * delta_time; // Radians per second - } else { - wander.angle -= change * delta_time; - } - - debug_lines.draw_line( - [position.x, position.y, position.z].into(), - [future_position.x, future_position.y, future_position.z].into(), - [1.0, 0.05, 0.65, 1.0].into(), - ); - - debug_lines.draw_direction( - [future_position.x, future_position.y, future_position.z].into(), - [direction.x, direction.y, direction.z].into(), - [1.0, 0.05, 0.65, 1.0].into(), - ); - } - } -} +//use amethyst::core::transform::Transform; +//use amethyst::core::Time; +//use amethyst::ecs::*; +//use amethyst::renderer::*; + +//use crate::components::creatures; +//use rand::{thread_rng, Rng}; + +//pub struct WanderSystem; +//impl<'s> System<'s> for WanderSystem { +//type SystemData = ( +//WriteStorage<'s, creatures::Wander>, +//WriteStorage<'s, creatures::Movement>, +//ReadStorage<'s, Transform>, +//Write<'s, DebugLines>, +//Read<'s, Time>, +//); + +//fn run( +//&mut self, +//(mut wanders, mut movements, locals, mut debug_lines, time): Self::SystemData, +//) { +//let delta_time = time.delta_seconds(); +//let mut rng = thread_rng(); + +//for (wander, movement, local) in (&mut wanders, &mut movements, &locals).join() { +//let position = local.translation(); +//let future_position = position + movement.velocity * 0.5; + +//let direction = wander.get_direction(); +//let target = future_position + direction; + +//let desired_velocity = target - position; + +//movement.velocity += desired_velocity * delta_time; + +//let change = 10.0; +//if rng.gen::() { +//wander.angle += change * delta_time; // Radians per second +//} else { +//wander.angle -= change * delta_time; +//} + +//debug_lines.draw_line( +//[position.x, position.y, position.z].into(), +//[future_position.x, future_position.y, future_position.z].into(), +//[1.0, 0.05, 0.65, 1.0].into(), +//); + +//debug_lines.draw_direction( +//[future_position.x, future_position.y, future_position.z].into(), +//[direction.x, direction.y, direction.z].into(), +//[1.0, 0.05, 0.65, 1.0].into(), +//); +//} +//} +//} diff --git a/src/systems/collision.rs b/src/systems/collision.rs index 38fe361..dfcf6e6 100644 --- a/src/systems/collision.rs +++ b/src/systems/collision.rs @@ -8,29 +8,29 @@ use thread_profiler::profile_scope; use crate::components::collider; use crate::components::creatures; -use crate::resources::world_bounds::*; - -pub struct EnforceBoundsSystem; - -impl<'s> System<'s> for EnforceBoundsSystem { - type SystemData = (WriteStorage<'s, Transform>, Read<'s, WorldBounds>); - - fn run(&mut self, (mut locals, bounds): Self::SystemData) { - for local in (&mut locals).join() { - if local.translation().x > bounds.right { - local.translation_mut().x = bounds.right; - } else if local.translation().x < bounds.left { - local.translation_mut().x = bounds.left; - } - - if local.translation().y > bounds.top { - local.translation_mut().y = bounds.top; - } else if local.translation().y < bounds.bottom { - local.translation_mut().y = bounds.bottom; - } - } - } -} +//use crate::resources::world_bounds::*; + +//pub struct EnforceBoundsSystem; + +//impl<'s> System<'s> for EnforceBoundsSystem { +//type SystemData = (WriteStorage<'s, Transform>, Read<'s, WorldBounds>); + +//fn run(&mut self, (mut locals, bounds): Self::SystemData) { +//for local in (&mut locals).join() { +//if local.translation().x > bounds.right { +//local.translation_mut().x = bounds.right; +//} else if local.translation().x < bounds.left { +//local.translation_mut().x = bounds.left; +//} + +//if local.translation().y > bounds.top { +//local.translation_mut().y = bounds.top; +//} else if local.translation().y < bounds.bottom { +//local.translation_mut().y = bounds.bottom; +//} +//} +//} +//} #[derive(Debug, Clone)] pub struct CollisionEvent { diff --git a/src/systems/experimental/boids.rs b/src/systems/experimental/boids.rs new file mode 100644 index 0000000..b19b949 --- /dev/null +++ b/src/systems/experimental/boids.rs @@ -0,0 +1,264 @@ +use amethyst::{ + core::{ + nalgebra::{Vector3, Vector4}, + transform::GlobalTransform, + Named, Time, + }, + ecs::{Entities, Join, Read, ReadStorage, System, WriteStorage}, +}; + +use std::collections::HashMap; +use std::f32; + +use crate::{ + components::{boids::*, creatures::Movement, perception::DetectedEntities}, + resources::world_bounds::WorldBounds, +}; + +pub struct FlockingSystem; + +impl<'s> System<'s> for FlockingSystem { + type SystemData = ( + ReadStorage<'s, Named>, + ReadStorage<'s, FlockingRule>, + ReadStorage<'s, DetectedEntities>, + ReadStorage<'s, GlobalTransform>, + WriteStorage<'s, Movement>, + Read<'s, Time>, + ); + + fn run( + &mut self, + (names, flocking_rules, detected_entities, globals, mut movements, time): Self::SystemData, + ) { + let delta_time = time.delta_seconds(); + for (name, rule, detected, global, movement) in ( + &names, + &flocking_rules, + &detected_entities, + &globals, + &mut movements, + ) + .join() + { + let pos = Vector4::from(global.as_ref()[3]).xyz(); + let mut average_position = pos; + let mut count = 1; + for (other_name, other_global, _) in (&names, &globals, &detected.entities).join() { + if other_name.name == name.name { + let other_pos = Vector4::from(other_global.as_ref()[3]).xyz(); + average_position += other_pos; + count += 1; + } + } + if count >= 2 { + average_position /= count as f32; + let diff_vector = average_position - pos; + movement.velocity += delta_time * rule.strength * diff_vector; + } + } + } +} + +pub struct MatchVelocitySystem; + +impl<'s> System<'s> for MatchVelocitySystem { + type SystemData = ( + Entities<'s>, + ReadStorage<'s, Named>, + ReadStorage<'s, MatchVelocityRule>, + ReadStorage<'s, DetectedEntities>, + WriteStorage<'s, Movement>, + Read<'s, Time>, + ); + + fn run( + &mut self, + (entities, names, velocity_rules, detected_entities, mut movements, time): Self::SystemData, + ) { + let delta_time = time.delta_seconds(); + + let mut perceived_velocities = HashMap::new(); + for (entity, name, _, detected) in + (&entities, &names, &velocity_rules, &detected_entities).join() + { + let mut average_velocity = Vector3::new(0.0, 0.0, 0.0); + let mut count = 0; + for (other_name, other_movement, _) in (&names, &movements, &detected.entities).join() { + if other_name.name == name.name { + average_velocity += other_movement.velocity; + count += 1; + } + } + if count >= 1 { + perceived_velocities.insert(entity, average_velocity / count as f32); + } + } + for (entity, _, rule, _, movement) in ( + &entities, + &names, + &velocity_rules, + &detected_entities, + &mut movements, + ) + .join() + { + match perceived_velocities.get(&entity) { + Some(v) => movement.velocity += delta_time * rule.strength * v, + None => (), + } + } + } +} + +pub struct MinimumDistanceSystem; + +impl<'s> System<'s> for MinimumDistanceSystem { + type SystemData = ( + ReadStorage<'s, Named>, + ReadStorage<'s, MinimumDistanceRule>, + ReadStorage<'s, DetectedEntities>, + ReadStorage<'s, GlobalTransform>, + WriteStorage<'s, Movement>, + Read<'s, Time>, + ); + + fn run( + &mut self, + (names, min_distances, detected_entities, globals, mut movements, time): Self::SystemData, + ) { + let delta_time = time.delta_seconds(); + for (name, min_distance, detected, global, movement) in ( + &names, + &min_distances, + &detected_entities, + &globals, + &mut movements, + ) + .join() + { + let sq_min_dist = min_distance.minimum * min_distance.minimum; + let pos = Vector4::from(global.as_ref()[3]).xyz(); + let mut total_diff = Vector3::new(0.0, 0.0, 0.0); + for (other_name, other_global, _) in (&names, &globals, &detected.entities).join() { + if name.name == other_name.name { + let other_pos = Vector4::from(other_global.as_ref()[3]).xyz(); + let diff = pos - other_pos; + let dist = diff.norm_squared(); + if dist < sq_min_dist { + total_diff += diff; + } + } + } + movement.velocity += delta_time * min_distance.strength * total_diff; + } + } +} + +pub struct AvoidSystem; + +impl<'s> System<'s> for AvoidSystem { + type SystemData = ( + ReadStorage<'s, Named>, + ReadStorage<'s, AvoidRule>, + ReadStorage<'s, DetectedEntities>, + ReadStorage<'s, GlobalTransform>, + WriteStorage<'s, Movement>, + Read<'s, Time>, + ); + + fn run( + &mut self, + (names, avoid_rules, detected_entities, globals, mut movements, time): Self::SystemData, + ) { + let delta_time = time.delta_seconds(); + for (rule, detected, global, movement) in + (&avoid_rules, &detected_entities, &globals, &mut movements).join() + { + let pos = Vector4::from(global.as_ref()[3]).xyz(); + let mut min_sq_dist = std::f32::MAX; + let mut min_pos = pos; + for (other_name, other_global, _) in (&names, &globals, &detected.entities).join() { + if rule.names.contains(&(&*other_name.name).to_string()) { + let other_pos = Vector4::from(other_global.as_ref()[3]).xyz(); + let sq_dist = (other_pos - pos).norm_squared(); + if sq_dist < min_sq_dist { + min_sq_dist = sq_dist; + min_pos = other_pos; + } + } + } + let diff_vector = min_pos - pos; + movement.velocity -= delta_time * rule.strength * diff_vector; + } + } +} + +pub struct SeekSystem; + +impl<'s> System<'s> for SeekSystem { + type SystemData = ( + ReadStorage<'s, Named>, + ReadStorage<'s, SeekRule>, + ReadStorage<'s, DetectedEntities>, + ReadStorage<'s, GlobalTransform>, + WriteStorage<'s, Movement>, + Read<'s, Time>, + ); + + fn run( + &mut self, + (names, seek_rules, detected_entities, globals, mut movements, time): Self::SystemData, + ) { + let delta_time = time.delta_seconds(); + for (rule, detected, global, movement) in + (&seek_rules, &detected_entities, &globals, &mut movements).join() + { + let pos = Vector4::from(global.as_ref()[3]).xyz(); + let mut min_sq_dist = std::f32::MAX; + let mut min_pos = pos; + for (other_name, other_global, _) in (&names, &globals, &detected.entities).join() { + if rule.names.contains(&(&*other_name.name).to_string()) { + let other_pos = Vector4::from(other_global.as_ref()[3]).xyz(); + let sq_dist = (other_pos - pos).norm_squared(); + if sq_dist < min_sq_dist { + min_sq_dist = sq_dist; + min_pos = other_pos; + } + } + } + let diff_vector = min_pos - pos; + movement.velocity += delta_time * rule.strength * diff_vector; + } + } +} + +pub struct WorldBoundsSystem; + +impl<'s> System<'s> for WorldBoundsSystem { + type SystemData = ( + Read<'s, WorldBounds>, + ReadStorage<'s, WorldBoundsRule>, + ReadStorage<'s, GlobalTransform>, + WriteStorage<'s, Movement>, + Read<'s, Time>, + ); + + fn run(&mut self, (bounds, bounds_rules, globals, mut movements, time): Self::SystemData) { + let delta_time = time.delta_seconds(); + for (rule, global, movement) in (&bounds_rules, &globals, &mut movements).join() { + let pos = Vector4::from(global.as_ref()[3]).xyz(); + + if pos[0] < bounds.left { + movement.velocity[0] += delta_time * rule.strength * (bounds.left - pos[0]); + } else if pos[0] > bounds.right { + movement.velocity[0] -= delta_time * rule.strength * (pos[0] - bounds.right); + } + if pos[1] < bounds.bottom { + movement.velocity[1] += delta_time * rule.strength * (bounds.bottom - pos[1]); + } else if pos[1] > bounds.top { + movement.velocity[1] -= delta_time * rule.strength * (pos[1] - bounds.top); + } + } + } +} diff --git a/src/systems/experimental/mod.rs b/src/systems/experimental/mod.rs index d9db8c2..8e7677c 100644 --- a/src/systems/experimental/mod.rs +++ b/src/systems/experimental/mod.rs @@ -1 +1,2 @@ +pub mod boids; pub mod perception; diff --git a/src/systems/experimental/perception.rs b/src/systems/experimental/perception.rs index b21af37..b9c64d6 100644 --- a/src/systems/experimental/perception.rs +++ b/src/systems/experimental/perception.rs @@ -38,23 +38,17 @@ impl<'s> System<'s> for EntityDetectionSystem { for (entity, perception, mut detected, global) in (&entities, &perceptions, &mut detected_entities, &globals).join() { - detected.entities = Vec::new(); + detected.entities = BitSet::new(); let nearby_entities = grid.query(global, perception.range); let pos = Vector4::from(global.as_ref()[3]).xyz(); let sq_range = perception.range * perception.range; - let mut nearby_entities_bitset = BitSet::new(); - for other_entity in &nearby_entities { - if entity == *other_entity { + for (other_entity, other_global, _) in (&entities, &globals, &nearby_entities).join() { + if entity == other_entity { continue; } - nearby_entities_bitset.add(other_entity.id()); - } - for (other_entity, other_global, _) in - (&entities, &globals, &nearby_entities_bitset).join() - { let other_pos = Vector4::from(other_global.as_ref()[3]).xyz(); if (pos - other_pos).norm_squared() < sq_range { - detected.entities.push(other_entity); + detected.entities.add(other_entity.id()); } } } @@ -90,8 +84,7 @@ impl<'s> System<'s> for DebugEntityDetectionSystem { fn run(&mut self, (detected_entities, globals, mut debug_lines): Self::SystemData) { for (detected, global) in (&detected_entities, &globals).join() { let pos = Vector4::from(global.as_ref()[3]).xyz(); - for other_entity in &detected.entities { - let other_global = globals.get(*other_entity).unwrap(); + for (other_global, _) in (&globals, &detected.entities).join() { let other_pos = Vector4::from(other_global.as_ref()[3]).xyz(); debug_lines.draw_line( [pos[0], pos[1], 0.0].into(), diff --git a/src/systems/spawner.rs b/src/systems/spawner.rs index d318327..45e502a 100644 --- a/src/systems/spawner.rs +++ b/src/systems/spawner.rs @@ -25,16 +25,16 @@ struct CreatureTypeDistribution { } impl Distribution for Standard { fn sample(&self, rng: &mut R) -> CreatureTypeDistribution { - match rng.gen_range(0, 3) { + match rng.gen_range(0, 6) { 0 => CreatureTypeDistribution { - creature_type: "Herbivore".to_string(), - }, - 1 => CreatureTypeDistribution { creature_type: "Carnivore".to_string(), }, - _ => CreatureTypeDistribution { + 1 | 2 | 3 => CreatureTypeDistribution { creature_type: "Plant".to_string(), }, + _ => CreatureTypeDistribution { + creature_type: "Herbivore".to_string(), + }, } } } @@ -94,7 +94,7 @@ impl<'s> System<'s> for DebugSpawnTriggerSystem { self.timer_to_next_spawn -= delta_seconds; if self.timer_to_next_spawn <= 0.0 { let mut creature_entity_builder = lazy_update.create_entity(&entities); - self.timer_to_next_spawn = 1.5; + self.timer_to_next_spawn = 0.5; let mut rng = thread_rng(); let x = (rng.next_u32() % 100) as f32 / 5.0 - 10.0; let y = (rng.next_u32() % 100) as f32 / 5.0 - 10.0;