fix: collision tunneling, spawn at surface, physics substepping
- 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
This commit is contained in:
+88
-46
@@ -33,65 +33,107 @@ pub fn resolve_grid_collision(grid: &Grid, body: &mut SubBody) -> CollisionResul
|
||||
let min_y = (body.y - r).floor() as i32;
|
||||
let max_y = (body.y + r).ceil() as i32;
|
||||
|
||||
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;
|
||||
}
|
||||
for _iteration in 0..3 {
|
||||
let mut any_collision = false;
|
||||
|
||||
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;
|
||||
for cy in min_y..=max_y {
|
||||
for cx in min_x..=max_x {
|
||||
if !grid.in_bounds(cx, cy) {
|
||||
continue;
|
||||
}
|
||||
if cell.material == MaterialId::Acid {
|
||||
result.touching_acid = true;
|
||||
let cell = grid.get(cx, cy);
|
||||
if cell.is_empty() {
|
||||
continue;
|
||||
}
|
||||
apply_liquid_drag(body, cell.density());
|
||||
continue;
|
||||
}
|
||||
|
||||
if cell.material == MaterialId::Fire {
|
||||
result.touching_fire = true;
|
||||
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.is_solid() {
|
||||
let closest_x = body.x.max(cx as f32).min((cx + 1) as f32);
|
||||
let closest_y = body.y.max(cy as f32).min((cy + 1) as f32);
|
||||
let dx = body.x - closest_x;
|
||||
let dy = body.y - closest_y;
|
||||
let dist_sq = dx * dx + dy * dy;
|
||||
if dist_sq < r * r {
|
||||
let dist = dist_sq.sqrt();
|
||||
if dist > 0.0001 {
|
||||
let overlap = r - dist;
|
||||
let nx = dx / dist;
|
||||
let ny = dy / dist;
|
||||
body.x += nx * overlap;
|
||||
body.y += ny * overlap;
|
||||
if ny < -0.5 {
|
||||
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 bcx = cx as f32 + 0.5;
|
||||
let bcy = cy as f32 + 0.5;
|
||||
let dx = body.x - bcx;
|
||||
let dy = body.y - bcy;
|
||||
let dist = (dx * dx + dy * dy).sqrt();
|
||||
if dist > 0.0001 {
|
||||
body.x = bcx + dx / dist * r * 1.1;
|
||||
body.y = bcy + dy / dist * r * 1.1;
|
||||
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
|
||||
|
||||
+14
-3
@@ -87,14 +87,18 @@ pub struct VerletSolver {
|
||||
pub gravity: f32,
|
||||
pub damping: f32,
|
||||
pub dt: f32,
|
||||
pub max_vel: f32,
|
||||
pub substeps: u32,
|
||||
}
|
||||
|
||||
impl VerletSolver {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
gravity: 0.3,
|
||||
gravity: 0.08,
|
||||
damping: 0.98,
|
||||
dt: 1.0,
|
||||
max_vel: 0.8,
|
||||
substeps: 4,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +107,15 @@ impl VerletSolver {
|
||||
if !b.alive {
|
||||
continue;
|
||||
}
|
||||
let vx = (b.x - b.old_x) * self.damping;
|
||||
let vy = (b.y - b.old_y) * self.damping;
|
||||
let mut vx = (b.x - b.old_x) * self.damping;
|
||||
let mut vy = (b.y - b.old_y) * self.damping;
|
||||
|
||||
let v_mag = (vx * vx + vy * vy).sqrt();
|
||||
if v_mag > self.max_vel {
|
||||
vx = vx / v_mag * self.max_vel;
|
||||
vy = vy / v_mag * self.max_vel;
|
||||
}
|
||||
|
||||
b.old_x = b.x;
|
||||
b.old_y = b.y;
|
||||
b.x += vx + b.ax * self.dt * self.dt;
|
||||
|
||||
Reference in New Issue
Block a user