density repulsion
Some checks failed
Build Bevy Game (Linux) / build (push) Failing after 18m36s

This commit is contained in:
Crizomb 2025-12-28 00:17:47 +01:00
parent 57db0aff22
commit 6aeda8be14
7 changed files with 171 additions and 19 deletions

80
src/density_grid.rs Normal file
View file

@ -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<DensityGrid>, 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<DensityGrid>, 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<DensityGrid>,
mut query: Query<(&mut PhysicsBody, &Transform), With<DensityObject>>,
) {
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;
}
}

1
src/globals.rs Normal file
View file

@ -0,0 +1 @@
pub static MAX_MAP_WIDTH: usize = 1024;

View file

@ -10,14 +10,9 @@ pub struct Kirby {
pub fn kirby_spawn(mut commands: Commands, asset_server: Res<AssetServer>) { pub fn kirby_spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
let sprite = Sprite::from_image(asset_server.load("sprites/kirby.png")); let sprite = Sprite::from_image(asset_server.load("sprites/kirby.png"));
let body = PhysicsBody { let body = PhysicsBody { mass: 10.0, force: Vec2::ZERO, velocity: Vec2::ZERO, drag: 0.05 };
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); 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<ButtonInput<KeyCode>>) -> Vec2 { pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
@ -43,16 +38,13 @@ pub fn get_dir(keys: Res<ButtonInput<KeyCode>>) -> Vec2 {
dir.normalize_or_zero() dir.normalize_or_zero()
} }
pub fn kirby_player_move( pub fn kirby_player_move(keys: Res<ButtonInput<KeyCode>>, mut query: Query<(&mut PhysicsBody, &Kirby), With<Kirby>>) {
keys: Res<ButtonInput<KeyCode>>,
mut query: Query<(&mut PhysicsBody, &Kirby), With<Kirby>>,
) {
if keys.pressed(KeyCode::Space) { if keys.pressed(KeyCode::Space) {
println!("SUCKING"); println!("SUCKING");
} }
let dir = get_dir(keys); let dir = get_dir(keys);
for (mut body, kirby) in &mut query { for (mut body, kirby) in &mut query {
body.velocity += dir * kirby.speed_force; body.force += dir * kirby.speed_force;
} }
} }

57
src/lana.rs Normal file
View file

@ -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<AssetServer>) {
let texture: Handle<Image> = 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<Lana>>,
kirby_query: Query<(&Transform, &Kirby), With<Kirby>>,
) {
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,
}

View file

@ -1,12 +1,21 @@
use bevy::prelude::*; use bevy::prelude::*;
use camera::spawn_camera; 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_player_move;
use kirby::kirby_spawn; use kirby::kirby_spawn;
use lana::lana_move;
use lana::lana_spawn;
use map::MapBounds; use map::MapBounds;
use physics_body::integrate; use physics_body::integrate;
mod camera; mod camera;
mod density_grid;
mod globals;
mod kirby; mod kirby;
mod lana;
mod map; mod map;
mod physics_body; mod physics_body;
mod sphere_collider; mod sphere_collider;
@ -14,12 +23,15 @@ mod sphere_collider;
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.insert_resource(MapBounds { .insert_resource(MapBounds { min: Vec2::ONE * -600.0, max: Vec2::ONE * 600.0 })
min: Vec2::ONE * -400.0, .insert_resource(DensityGrid::new())
max: Vec2::ONE * 400.0,
})
.add_systems(Startup, spawn_camera) .add_systems(Startup, spawn_camera)
.add_systems(Startup, kirby_spawn) .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(); .run();
} }

View file

@ -35,7 +35,7 @@ pub fn integrate(
physicsbody.velocity += force * delta_secs / mass; physicsbody.velocity += force * delta_secs / mass;
physicsbody.velocity *= 1.0 - drag; 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.x += physicsbody.velocity.x * delta_secs;
transform.translation.y += physicsbody.velocity.y * 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) if (transform.translation.x < bounds.min.x && velocity.x < 0.0)
|| (transform.translation.x > bounds.max.x && velocity.x > 0.0) || (transform.translation.x > bounds.max.x && velocity.x > 0.0)
{ {

View file

@ -5,3 +5,13 @@ use bevy::{prelude::*, render::renderer};
pub struct SphereCollider { pub struct SphereCollider {
pub radius: f32, 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
}
}