- Fix: rewrite resolve_grid_collision to push body out of solid cells when center is inside (was only pushing to small radius from center) - Fix: max velocity clamp (0.8) + 4x substepping prevents tunneling - Fix: player/goblin spawn at terrain surface instead of mid-air - Fix: gravity reduced from 0.3 to 0.08 for substepping - Add: headless mode (--headless-ticks N) dumps grid state to file - Add: velocity zeroing on collision for stable resting
149 lines
5.0 KiB
Rust
149 lines
5.0 KiB
Rust
use crate::world::cell::MaterialId;
|
|
use crate::world::grid::Grid;
|
|
use crate::physics::verlet::SubBody;
|
|
|
|
pub struct CollisionResult {
|
|
pub on_ground: bool,
|
|
pub in_liquid: bool,
|
|
pub liquid_density: f32,
|
|
pub touching_lava: bool,
|
|
pub touching_fire: bool,
|
|
pub touching_acid: bool,
|
|
}
|
|
|
|
impl CollisionResult {
|
|
pub fn none() -> Self {
|
|
Self {
|
|
on_ground: false,
|
|
in_liquid: false,
|
|
liquid_density: 0.0,
|
|
touching_lava: false,
|
|
touching_fire: false,
|
|
touching_acid: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn resolve_grid_collision(grid: &Grid, body: &mut SubBody) -> CollisionResult {
|
|
let mut result = CollisionResult::none();
|
|
|
|
let r = body.radius;
|
|
let min_x = (body.x - r).floor() as i32;
|
|
let max_x = (body.x + r).ceil() as i32;
|
|
let min_y = (body.y - r).floor() as i32;
|
|
let max_y = (body.y + r).ceil() as i32;
|
|
|
|
for _iteration in 0..3 {
|
|
let mut any_collision = false;
|
|
|
|
for cy in min_y..=max_y {
|
|
for cx in min_x..=max_x {
|
|
if !grid.in_bounds(cx, cy) {
|
|
continue;
|
|
}
|
|
let cell = grid.get(cx, cy);
|
|
if cell.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
if cell.is_liquid() {
|
|
result.in_liquid = true;
|
|
result.liquid_density = result.liquid_density.max(cell.density());
|
|
if cell.material == MaterialId::Lava {
|
|
result.touching_lava = true;
|
|
}
|
|
if cell.material == MaterialId::Acid {
|
|
result.touching_acid = true;
|
|
}
|
|
apply_liquid_drag(body, cell.density());
|
|
continue;
|
|
}
|
|
|
|
if cell.material == MaterialId::Fire {
|
|
result.touching_fire = true;
|
|
continue;
|
|
}
|
|
|
|
if cell.is_solid() {
|
|
let cell_min_x = cx as f32;
|
|
let cell_max_x = (cx + 1) as f32;
|
|
let cell_min_y = cy as f32;
|
|
let cell_max_y = (cy + 1) as f32;
|
|
|
|
let inside_x = body.x >= cell_min_x && body.x < cell_max_x;
|
|
let inside_y = body.y >= cell_min_y && body.y < cell_max_y;
|
|
|
|
if inside_x && inside_y {
|
|
let dist_left = body.x - cell_min_x;
|
|
let dist_right = cell_max_x - body.x;
|
|
let dist_top = body.y - cell_min_y;
|
|
let dist_bottom = cell_max_y - body.y;
|
|
|
|
let min_dist = dist_left.min(dist_right).min(dist_top).min(dist_bottom);
|
|
|
|
if min_dist == dist_top {
|
|
body.y = cell_min_y - r;
|
|
result.on_ground = true;
|
|
} else if min_dist == dist_bottom {
|
|
body.y = cell_max_y + r;
|
|
} else if min_dist == dist_left {
|
|
body.x = cell_min_x - r;
|
|
} else {
|
|
body.x = cell_max_x + r;
|
|
}
|
|
|
|
let vy = body.vy();
|
|
if min_dist == dist_top && vy > 0.0 {
|
|
body.set_vel(body.vx(), 0.0);
|
|
}
|
|
let vx = body.vx();
|
|
if (min_dist == dist_left || min_dist == dist_right) && vx != 0.0 {
|
|
body.set_vel(0.0, body.vy());
|
|
}
|
|
|
|
any_collision = true;
|
|
} else {
|
|
let closest_x = body.x.max(cell_min_x).min(cell_max_x);
|
|
let closest_y = body.y.max(cell_min_y).min(cell_max_y);
|
|
let dx = body.x - closest_x;
|
|
let dy = body.y - closest_y;
|
|
let dist_sq = dx * dx + dy * dy;
|
|
|
|
if dist_sq < r * r && dist_sq > 0.0001 {
|
|
let dist = dist_sq.sqrt();
|
|
let overlap = r - dist;
|
|
let nx = dx / dist;
|
|
let ny = dy / dist;
|
|
body.x += nx * overlap;
|
|
body.y += ny * overlap;
|
|
|
|
if ny < -0.5 {
|
|
result.on_ground = true;
|
|
if body.vy() > 0.0 {
|
|
body.set_vel(body.vx(), 0.0);
|
|
}
|
|
}
|
|
|
|
any_collision = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !any_collision {
|
|
break;
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn apply_liquid_drag(body: &mut SubBody, density: f32) {
|
|
let drag = 1.0 - density * 0.08;
|
|
let drag = drag.max(0.5);
|
|
let vx = body.vx() * drag;
|
|
let vy = body.vy() * drag;
|
|
body.set_vel(vx, vy);
|
|
}
|