diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/content/entities/entity_wizard.rs | 47 | ||||
| -rw-r--r-- | src/input.rs | 4 | ||||
| -rw-r--r-- | src/sim/entity/mod.rs | 23 | ||||
| -rw-r--r-- | src/sim/lib/components.rs | 2 | ||||
| -rw-r--r-- | src/sim/rb_manager/character_impulses.rs | 136 | ||||
| -rw-r--r-- | src/sim/rb_manager/mod.rs | 1 | ||||
| -rw-r--r-- | src/sim/sim_manager/mod.rs | 6 |
7 files changed, 197 insertions, 22 deletions
diff --git a/src/content/entities/entity_wizard.rs b/src/content/entities/entity_wizard.rs index 6707f7c..3787e49 100644 --- a/src/content/entities/entity_wizard.rs +++ b/src/content/entities/entity_wizard.rs @@ -9,6 +9,7 @@ use crate::{ input::Input, sim::{ entity::{EntityBehaviour, EntityDef, EntityUpdateCtx}, + rb_manager::character_impulses::solve_character_collision_impulses, sim_manager::SimCtx, }, }; @@ -37,7 +38,7 @@ impl EntityBehaviour for WizardEntityBehaviour { self.movement_input_x = 0.0; } - if ctx.input_manager.pressed(Input::Up) { + if ctx.input_manager.pressed(Input::Jump) { self.jump = 0.06; } } @@ -87,7 +88,15 @@ impl EntityBehaviour for WizardEntityBehaviour { .rb_manager .physics_manager .world - .query_pipeline_with_filter( + .broad_phase + .as_query_pipeline( + ctx.rb_manager + .physics_manager + .world + .narrow_phase + .query_dispatcher(), + &ctx.rb_manager.physics_manager.world.bodies, + &ctx.rb_manager.physics_manager.world.colliders, QueryFilter::default().exclude_rigid_body(update_ctx.entity_data.rb_h.unwrap()), ); @@ -105,19 +114,49 @@ impl EntityBehaviour for WizardEntityBehaviour { .world .colliders .get(update_ctx.entity_data.collider_h.unwrap()) - .unwrap(); + .unwrap() + .clone(); // move_shape works in translations, not velocities: feed it the distance we // want to cover this step, and convert the allowed distance back to a velocity. + let mut collisions = Vec::new(); let movement = self.kinematic_controller.move_shape( delta_time, &query_pipeline, collider.shape(), rb.position(), self.acc_vel * delta_time, - |_| {}, + |col| collisions.push(col), + ); + + let mut query_pipeline_mut = ctx + .rb_manager + .physics_manager + .world + .broad_phase + .as_query_pipeline_mut( + ctx.rb_manager + .physics_manager + .world + .narrow_phase + .query_dispatcher(), + &mut ctx.rb_manager.physics_manager.world.bodies, + &mut ctx.rb_manager.physics_manager.world.colliders, + QueryFilter::default().exclude_rigid_body(update_ctx.entity_data.rb_h.unwrap()), + ); + + // apply collisions to other scene entities. Local port: rapier's own + // solve_character_collision_impulses panics with 2+ nearby dynamic bodies. + solve_character_collision_impulses( + &self.kinematic_controller, + delta_time, + &mut query_pipeline_mut, + collider.shape(), + update_ctx.entity_data.cells.as_mut().unwrap().mass(), + &collisions, ); + // update the entity's position let rb = ctx .rb_manager .physics_manager diff --git a/src/input.rs b/src/input.rs index 108514e..0697f29 100644 --- a/src/input.rs +++ b/src/input.rs @@ -9,7 +9,7 @@ use crate::{camera::Camera, sim::cell_manager::manager::CellManager}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Input { // movement - Up = 0, + Jump = 0, Left, Down, Right, @@ -44,7 +44,7 @@ pub enum Input { const INPUT_VAR_LEN: usize = 22; const KEYMAP: [(KeyCode, Input); INPUT_VAR_LEN] = [ - (KeyCode::KeyW, Input::Up), + (KeyCode::Space, Input::Jump), (KeyCode::KeyA, Input::Left), (KeyCode::KeyS, Input::Down), (KeyCode::KeyD, Input::Right), diff --git a/src/sim/entity/mod.rs b/src/sim/entity/mod.rs index 4a8eda4..59479a2 100644 --- a/src/sim/entity/mod.rs +++ b/src/sim/entity/mod.rs @@ -2,7 +2,6 @@ use glam::{IVec2, Vec2}; use rapier2d::{ dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, geometry::{Collider, ColliderHandle}, - math::Pose2, }; use crate::{ @@ -14,13 +13,6 @@ use crate::{ sprite_loader::load_sprite_to_cells, }; -fn mass_from_cells(cells: &[Cell]) -> f32 { - cells - .iter() - .fold(0.0, |acc, cur| acc + cur.material.def().density as f32) - * MASS_SCALING -} - pub struct EntityCells { pub size: IVec2, pub cells: Vec<Cell>, @@ -35,6 +27,13 @@ impl EntityCells { pub fn set_cell_at_local_position(&mut self, pos: IVec2, cell: Cell) { self.cells[pos.x as usize + pos.y as usize * self.size.x as usize] = cell; } + + pub fn mass(&self) -> f32 { + self.cells + .iter() + .fold(0.0, |acc, cur| acc + cur.material.def().density as f32) + * MASS_SCALING + } } impl Marchable for EntityCells { @@ -76,7 +75,7 @@ impl EntityDef { behaviour: Option<Box<dyn EntityBehaviour>>, ) -> Self { let collider = RbManager::convex_hull_collider_from_marchable(&cells, Some(10)) - .mass(mass_from_cells(&cells.cells)) + .mass(cells.mass()) .build(); EntityDef { @@ -95,7 +94,7 @@ impl EntityDef { let rb = RigidBodyBuilder::dynamic().translation(position).build(); let collider = RbManager::convex_hull_collider_from_marchable(&cells, Some(10)) - .mass(mass_from_cells(&cells.cells)) + .mass(cells.mass()) .build(); EntityDef { @@ -135,7 +134,7 @@ impl EntityDef { .build(); let collider = RbManager::convex_hull_collider_from_marchable(&cells, Some(10)) - .mass(mass_from_cells(&cells.cells)) + .mass(cells.mass()) .build(); EntityDef { @@ -259,7 +258,7 @@ impl Entity { pub fn compute_collider(&self) -> Option<Collider> { self.data.cells.as_ref().map(|cells| { RbManager::convex_hull_collider_from_marchable(cells, Some(10)) - .mass(mass_from_cells(&cells.cells)) + .mass(cells.mass()) .build() }) } diff --git a/src/sim/lib/components.rs b/src/sim/lib/components.rs index d43a567..ac62366 100644 --- a/src/sim/lib/components.rs +++ b/src/sim/lib/components.rs @@ -111,7 +111,7 @@ pub fn compute_components(cells: &[Cell], w: i32, h: i32) -> Vec<PositionedCompo as usize] = old_cell.cell; } - let pc_centre = (c.bounds[0] + c.bounds[1] + IVec2::ONE).as_vec2() / 2.0 ; + let pc_centre = (c.bounds[0] + c.bounds[1] + IVec2::ONE).as_vec2() / 2.0; let adjusted_pc_centre = pc_centre - (Vec2::new(w as f32, h as f32) / 2.0); let pc = PositionedComponent { position: adjusted_pc_centre, diff --git a/src/sim/rb_manager/character_impulses.rs b/src/sim/rb_manager/character_impulses.rs new file mode 100644 index 0000000..7016956 --- /dev/null +++ b/src/sim/rb_manager/character_impulses.rs @@ -0,0 +1,136 @@ +//! Local port of `KinematicCharacterController::solve_character_collision_impulses`. +//! +//! Rapier's version (0.35.2, still present on master) accumulates every nearby collider's +//! contact manifolds into one shared `Vec` and then slices it with `manifolds[prev_len..]`. +//! That assumes `contact_manifolds` appends, but parry's composite-shape paths (compound, +//! polyline, trimesh, ...) `mem::take` the output vec and rebuild it for the current pair +//! only. With two or more dynamic bodies inside the character's AABB the second call empties +//! the vec and the slice panics with "range start index 1 out of range for slice of length 0". +//! The character's collider here is a convex decomposition (a compound), so we hit it. +//! +//! This port uses a fresh scratch vec per collider, which is what rapier's own code intended. + +use rapier2d::{ + control::{CharacterCollision, CharacterLength, KinematicCharacterController}, + geometry::{ContactManifold, Shape}, + math::{Pose, Real}, + parry::{ + bounding_volume::BoundingVolume, + query::{DefaultQueryDispatcher, PersistentQueryDispatcher}, + }, + pipeline::QueryPipelineMut, +}; + +/// Apply approximate impulses to the dynamic bodies a kinematic character ran into. +pub fn solve_character_collision_impulses<'a>( + controller: &KinematicCharacterController, + dt: Real, + queries: &mut QueryPipelineMut, + character_shape: &dyn Shape, + character_mass: Real, + collisions: impl IntoIterator<Item = &'a CharacterCollision>, +) { + for collision in collisions { + solve_single_character_collision_impulse( + controller, + dt, + queries, + character_shape, + character_mass, + collision, + ); + } +} + +fn eval_length(length: CharacterLength, value: Real) -> Real { + match length { + CharacterLength::Relative(x) => value * x, + CharacterLength::Absolute(x) => x, + } +} + +fn solve_single_character_collision_impulse( + controller: &KinematicCharacterController, + dt: Real, + queries: &mut QueryPipelineMut, + character_shape: &dyn Shape, + character_mass: Real, + collision: &CharacterCollision, +) { + let extents = character_shape.compute_local_aabb().extents(); + let up_extent = extents.dot(controller.up.abs()); + let movement_to_transfer = + collision.hit.normal1 * collision.translation_remaining.dot(collision.hit.normal1); + // `KinematicCharacterController::predict_ground` is private; this is its body. + let prediction = eval_length(controller.offset, up_extent) + 0.05; + + let dispatcher = DefaultQueryDispatcher; + + let mut manifolds: Vec<ContactManifold> = Vec::new(); + // World pose of the collider each manifold was computed against: the `local_p2` + // points are in the collider's frame, which differs from its body's when offset. + let mut manifold_collider_poses: Vec<Pose> = Vec::new(); + let character_aabb = character_shape + .compute_aabb(&collision.character_pos) + .loosened(prediction); + + for (_, collider) in queries.as_ref().intersect_aabb_conservative(character_aabb) { + let Some(parent) = collider.parent() else { + continue; + }; + let Some(body) = queries.bodies.get(parent) else { + continue; + }; + if !body.is_dynamic() { + continue; + } + + let pos12 = collision.character_pos.inv_mul(collider.position()); + // Fresh vec per pair: parry may take/replace it rather than append. + let mut pair_manifolds: Vec<ContactManifold> = Vec::new(); + let _ = dispatcher.contact_manifolds( + &pos12, + character_shape, + collider.shape(), + prediction, + &mut pair_manifolds, + &mut None, + ); + + for mut m in pair_manifolds { + m.data.rigid_body2 = Some(parent); + m.data.normal = collision.character_pos.rotation * m.local_n1; + manifolds.push(m); + manifold_collider_poses.push(*collider.position()); + } + } + + let inv_dt = if dt != 0.0 { 1.0 / dt } else { 0.0 }; + let velocity_to_transfer = movement_to_transfer * inv_dt; + + for (manifold, collider_pos) in manifolds.iter().zip(manifold_collider_poses.iter()) { + let Some(body_handle) = manifold.data.rigid_body2 else { + continue; + }; + let Some(body) = queries.bodies.get_mut(body_handle) else { + continue; + }; + + for pt in &manifold.points { + if pt.dist <= prediction { + let body_mass = body.mass(); + let contact_point = *collider_pos * pt.local_p2; + let delta_vel_per_contact = (velocity_to_transfer + - body.velocity_at_point(contact_point)) + .dot(manifold.data.normal); + let mass_ratio = body_mass * character_mass / (body_mass + character_mass); + + body.apply_impulse_at_point( + manifold.data.normal * delta_vel_per_contact.max(0.0) * mass_ratio, + contact_point, + true, + ); + } + } + } +} diff --git a/src/sim/rb_manager/mod.rs b/src/sim/rb_manager/mod.rs index 1be024a..1da5e40 100644 --- a/src/sim/rb_manager/mod.rs +++ b/src/sim/rb_manager/mod.rs @@ -1,3 +1,4 @@ +pub mod character_impulses; pub mod debug_render; use fxhash::FxHashMap; diff --git a/src/sim/sim_manager/mod.rs b/src/sim/sim_manager/mod.rs index bbfd05e..1e6da4d 100644 --- a/src/sim/sim_manager/mod.rs +++ b/src/sim/sim_manager/mod.rs @@ -177,9 +177,9 @@ impl SimManager { if input_manager.pressed(Input::Action2) && let Some(entity_id) = written_entities_scope .get_entity_id_at_position(input_manager.world_mouse_pos.round().as_ivec2()) - { - self.atomize_entity(entity_id); - } + { + self.atomize_entity(entity_id); + } // TEST ATOMIZATION |
