diff --git a/src/density_grid.rs b/src/density_grid.rs new file mode 100644 index 0000000..1804aa3 --- /dev/null +++ b/src/density_grid.rs @@ -0,0 +1,80 @@ +use crate::globals::MAX_MAP_WIDTH; +use crate::physics_body::PhysicsBody; +use bevy::prelude::*; + +const CELL_SIZE: usize = 32; +const MAX_MAP_SIZE: usize = MAX_MAP_WIDTH * 2; +const ARRAY_WIDTH: usize = MAX_MAP_SIZE / CELL_SIZE; +const GRID_ARRAY_SIZE: usize = ARRAY_WIDTH * ARRAY_WIDTH; + +const DENSITY_FORCE_SCALE: f32 = 100.0; + +#[derive(Resource)] +pub struct DensityGrid { + grid: Box<[i32; GRID_ARRAY_SIZE]>, +} + +impl DensityGrid { + pub fn new() -> Self { + DensityGrid { grid: Box::new([0; GRID_ARRAY_SIZE]) } + } +} + +pub fn pos_to_grid_cell(pos: Vec2) -> UVec2 { + let ix = ((pos.x + MAX_MAP_WIDTH as f32) / CELL_SIZE as f32) as u32; + let iy = ((pos.y + MAX_MAP_WIDTH as f32) / CELL_SIZE as f32) as u32; + UVec2::new(ix, iy) +} + +fn get_index_from_uvec2(grid_cell: UVec2) -> usize { + if grid_cell.x >= ARRAY_WIDTH as u32 || grid_cell.y >= ARRAY_WIDTH as u32 { + panic!("Index out of bounds: {:?}", grid_cell); + } + (grid_cell.x + grid_cell.y * ARRAY_WIDTH as u32) as usize +} + +#[derive(Component, Default)] +pub struct DensityObject {} + +pub fn init_density_object(mut density_grid: ResMut, mut query: Query<&Transform>) { + for transform in &mut query { + let grid_cell = pos_to_grid_cell(transform.translation.xy()); + // println!("grid cell {:?}", grid_cell); + let index = get_index_from_uvec2(grid_cell); + density_grid.grid[index] += 1; + } +} + +pub fn density_grid_update(mut density_grid: ResMut, query: Query<&Transform>) { + density_grid.grid.fill(0); + + for transform in &query { + let cell = pos_to_grid_cell(transform.translation.xy()); + let idx = get_index_from_uvec2(cell); + density_grid.grid[idx] += 1; + } +} + +pub fn density_grid_force( + density_grid: Res, + mut query: Query<(&mut PhysicsBody, &Transform), With>, +) { + for (mut physics_body, transform) in &mut query { + let grid_cell = pos_to_grid_cell(transform.translation.xy()); + let mut density_gradient = Vec2::ZERO; + + for dx in [-1, 0, 1] { + for dy in [-1, 0, 1] { + let offset = IVec2 { x: dx, y: dy }; + let neighbor_cell = (offset + grid_cell.as_ivec2()).as_uvec2(); + let index = get_index_from_uvec2(neighbor_cell); + let density = density_grid.grid[index]; + + // println!("offset : {:?} density: {:?}", offset.as_vec2(), density); + density_gradient += offset.as_vec2() * density as f32; + } + } + // println!("density : {:?}", density_gradient); + physics_body.force -= density_gradient * DENSITY_FORCE_SCALE; + } +} diff --git a/src/globals.rs b/src/globals.rs new file mode 100644 index 0000000..f58da2d --- /dev/null +++ b/src/globals.rs @@ -0,0 +1 @@ +pub static MAX_MAP_WIDTH: usize = 1024; diff --git a/src/kirby.rs b/src/kirby.rs index e17b7b1..202306d 100644 --- a/src/kirby.rs +++ b/src/kirby.rs @@ -10,14 +10,9 @@ pub struct Kirby { pub fn kirby_spawn(mut commands: Commands, asset_server: Res) { let sprite = Sprite::from_image(asset_server.load("sprites/kirby.png")); - let body = PhysicsBody { - mass: 10.0, - force: Vec2::ZERO, - velocity: Vec2::ZERO, - drag: 0.05, - }; + let body = PhysicsBody { mass: 10.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.05 }; let transform = Transform::from_xyz(0.0, 0.0, 0.0).with_scale(Vec3::ONE * 0.25); - commands.spawn((Kirby { speed_force: 50.0 }, transform, sprite, body)); + commands.spawn((Kirby { speed_force: 10000.0 }, transform, sprite, body)); } pub fn get_dir(keys: Res>) -> Vec2 { @@ -43,16 +38,13 @@ pub fn get_dir(keys: Res>) -> Vec2 { dir.normalize_or_zero() } -pub fn kirby_player_move( - keys: Res>, - mut query: Query<(&mut PhysicsBody, &Kirby), With>, -) { +pub fn kirby_player_move(keys: Res>, mut query: Query<(&mut PhysicsBody, &Kirby), With>) { if keys.pressed(KeyCode::Space) { println!("SUCKING"); } let dir = get_dir(keys); for (mut body, kirby) in &mut query { - body.velocity += dir * kirby.speed_force; + body.force += dir * kirby.speed_force; } } diff --git a/src/lana.rs b/src/lana.rs new file mode 100644 index 0000000..66e6ef0 --- /dev/null +++ b/src/lana.rs @@ -0,0 +1,57 @@ +use crate::density_grid::DensityObject; +use crate::kirby::Kirby; +use crate::physics_body::PhysicsBody; +use crate::sphere_collider::SphereCollider; +use bevy::prelude::*; +use rand::Rng; +use std::f32::consts::TAU; + +#[derive(Component)] +#[require(Sprite, PhysicsBody, SphereCollider, DensityObject)] +pub struct Lana { + move_force: f32, +} + +pub fn lana_spawn(mut commands: Commands, asset_server: Res) { + let texture: Handle = asset_server.load("sprites/lana.png"); + let mut rng = rand::rng(); + + for _ in 0..1_000 { + let angle = rng.random_range(0.0..TAU); + let pos = Vec2::from_angle(angle) * 500.0; + + commands.spawn(( + Lana { move_force: 3000.0 }, + Sprite { image: texture.clone(), ..default() }, + Transform::from_translation(pos.extend(0.0)).with_scale(Vec3::splat(0.02)), + PhysicsBody { mass: 10.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.05 }, + )); + } +} + +pub fn lana_move( + mut lana_query: Query<(&mut PhysicsBody, &Transform, &Lana), With>, + kirby_query: Query<(&Transform, &Kirby), With>, +) { + for (mut lana_body, lana_transform, lana) in &mut lana_query { + let mut nearest_kirby_pos = Vec3::ZERO; + let mut nearest_squared = f32::MAX; + + for (kirby_transform, _kirby) in kirby_query { + let dist_squared = kirby_transform.translation.distance_squared(lana_transform.translation); + if (dist_squared) < nearest_squared { + nearest_squared = dist_squared; + nearest_kirby_pos = kirby_transform.translation; + } + } + + let dir = (nearest_kirby_pos - lana_transform.translation).normalize().xy(); + lana_body.force += dir * lana.move_force; + } +} + +#[derive(Component)] +#[require(Sprite, Transform)] +pub struct KirbySuction { + box_size: Vec2, +} diff --git a/src/main.rs b/src/main.rs index f2fd2a2..173b145 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,21 @@ use bevy::prelude::*; use camera::spawn_camera; +use density_grid::DensityGrid; +use density_grid::density_grid_force; +use density_grid::density_grid_update; +use density_grid::init_density_object; use kirby::kirby_player_move; use kirby::kirby_spawn; +use lana::lana_move; +use lana::lana_spawn; use map::MapBounds; use physics_body::integrate; mod camera; +mod density_grid; +mod globals; mod kirby; +mod lana; mod map; mod physics_body; mod sphere_collider; @@ -14,12 +23,15 @@ mod sphere_collider; fn main() { App::new() .add_plugins(DefaultPlugins) - .insert_resource(MapBounds { - min: Vec2::ONE * -400.0, - max: Vec2::ONE * 400.0, - }) + .insert_resource(MapBounds { min: Vec2::ONE * -600.0, max: Vec2::ONE * 600.0 }) + .insert_resource(DensityGrid::new()) .add_systems(Startup, spawn_camera) .add_systems(Startup, kirby_spawn) - .add_systems(FixedUpdate, (kirby_player_move, integrate).chain()) + .add_systems(Startup, lana_spawn) + .add_systems(PostStartup, init_density_object) + .add_systems(FixedUpdate, (density_grid_update, density_grid_force).chain()) + .add_systems(FixedUpdate, kirby_player_move) + .add_systems(FixedUpdate, lana_move) + .add_systems(FixedPostUpdate, integrate) .run(); } diff --git a/src/physics_body.rs b/src/physics_body.rs index f027df6..90b5cbd 100644 --- a/src/physics_body.rs +++ b/src/physics_body.rs @@ -35,7 +35,7 @@ pub fn integrate( physicsbody.velocity += force * delta_secs / mass; physicsbody.velocity *= 1.0 - drag; - bounce(&mut physicsbody.velocity, &transform, &bounds); + map_bounce(&mut physicsbody.velocity, &transform, &bounds); transform.translation.x += physicsbody.velocity.x * delta_secs; transform.translation.y += physicsbody.velocity.y * delta_secs; @@ -44,7 +44,7 @@ pub fn integrate( } } -fn bounce(velocity: &mut Vec2, transform: &Transform, bounds: &MapBounds) { +fn map_bounce(velocity: &mut Vec2, transform: &Transform, bounds: &MapBounds) { if (transform.translation.x < bounds.min.x && velocity.x < 0.0) || (transform.translation.x > bounds.max.x && velocity.x > 0.0) { diff --git a/src/sphere_collider.rs b/src/sphere_collider.rs index bf28d84..770ea63 100644 --- a/src/sphere_collider.rs +++ b/src/sphere_collider.rs @@ -5,3 +5,13 @@ use bevy::{prelude::*, render::renderer}; pub struct SphereCollider { pub radius: f32, } + +impl SphereCollider { + fn point_inside(&self, self_pos: Vec3, point: Vec3) -> bool { + (self_pos - point).length_squared() < self.radius * self.radius + } + fn collides(&self, self_pos: Vec3, other: &SphereCollider, other_pos: Vec3) -> bool { + let radius_sum = self.radius + other.radius; + (self_pos - other_pos).length_squared() < radius_sum * radius_sum + } +}