fix: AABB capsule collider for rigid entities, sliding on surfaces
- Replace per-body collision with single AABB collider for rigid entities - Entity has half_w/half_h defining the collider box - Separate axis resolution: X first (min-penetration push), then Y (velocity-direction push to handle floor/ceiling correctly) - Sliding: when hitting a wall, only X velocity is zeroed, Y continues - check_on_ground uses AABB bottom edge, not individual bodies - Fix: X resolution uses minimum penetration (was velocity-based, which pushed player through walls when velocity was zero) - Default scene: added water pool, lava pool, wood structure, sand dune, acid pool, stone wall obstacle - 28/28 tests passing
This commit is contained in:
+131
-131
@@ -12,16 +12,16 @@ Camera: (85, 230)
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40 @@@@@
|
||||
41 @@@@@@
|
||||
42 @@@@@@
|
||||
43 @@@@@ """""""""""""
|
||||
44""" @@@@@ "":::::::::::::"""
|
||||
45:::""" """::::::::::::::::::""
|
||||
46::::::"" "":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T @@@@@ ...
|
||||
41 T T ## @@@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -41,16 +41,16 @@ Entities: 1
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40 @@@@@
|
||||
41 @@@@@@
|
||||
42 @@@@@@
|
||||
43 @@@@@ """""""""""""
|
||||
44""" @@@@@ "":::::::::::::"""
|
||||
45:::""" """::::::::::::::::::""
|
||||
46::::::"" "":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T @@@@@ ...
|
||||
41 T T ## @@@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -71,16 +71,16 @@ Alive entities: Player(hp=100, pos=(125.0, 242.11879))
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40 @@@@@
|
||||
41 @@@@@@
|
||||
42 @@@@@@
|
||||
43 @@@@@ """""""""""""
|
||||
44""" @@@@@ "":::::::::::::"""
|
||||
45:::""" """::::::::::::::::::""
|
||||
46::::::"" "":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T @@@@@ ...
|
||||
41 T T ## @@@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -101,16 +101,16 @@ Alive entities: Player(hp=100, pos=(125.0, 242.38817))
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40 @@@@@
|
||||
41 @@@@@@
|
||||
42 @@@@@@
|
||||
43 @@@@@ """""""""""""
|
||||
44""" @@@@@ "":::::::::::::"""
|
||||
45:::""" """::::::::::::::::::""
|
||||
46::::::"" "":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T @@@@@ ...
|
||||
41 T T ## @@@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -130,16 +130,16 @@ Alive entities: Player(hp=100, pos=(125.0, 242.79922))
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41 @@@@@
|
||||
42 @@@@@@
|
||||
43 @@@@@@ """""""""""""
|
||||
44""" @@@@@ "":::::::::::::"""
|
||||
45:::""" @@@@@ """::::::::::::::::::""
|
||||
46::::::"" "":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -154,21 +154,22 @@ Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.3436))
|
||||
|
||||
=== Tick 10 ===
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42 @@@@@
|
||||
43 @@@@@@ """""""""""""
|
||||
44""" @@@@@@ "":::::::::::::"""
|
||||
45:::""" @@@@@ """::::::::::::::::::""
|
||||
46::::::"" @@@@@"":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -178,27 +179,27 @@ Alive entities: Player(hp=100, pos=(125.0, 243.3436))
|
||||
53????????????????????????????????????????????????????????????????????????????????
|
||||
54????????????????????????????????????????????????????????????????????????????????
|
||||
55????????????????????????????????????????????????????????????????????????????????
|
||||
56????????????????????????????????????????????????????????????????????????????????
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,244.0) bodies=27/27 on_fire=false"
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||
Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(125.0, 244.01338))
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||
|
||||
=== Tick 12 ===
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42 @@@@@
|
||||
43 @@@@@@ """""""""""""
|
||||
44""" @@@@@@ "":::::::::::::"""
|
||||
45:::""" @@@@@ """::::::::::::::::::""
|
||||
46::::::"" @@@@@"":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -208,27 +209,27 @@ Alive entities: Player(hp=100, pos=(125.0, 244.01338))
|
||||
53????????????????????????????????????????????????????????????????????????????????
|
||||
54????????????????????????????????????????????????????????????????????????????????
|
||||
55????????????????????????????????????????????????????????????????????????????????
|
||||
56????????????????????????????????????????????????????????????????????????????????
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,244.5) bodies=27/27 on_fire=false"
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||
Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(125.0, 244.5))
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||
|
||||
=== Tick 14 ===
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42 @@@@@
|
||||
43 @@@@@@ """""""""""""
|
||||
44""" @@@@@@ "":::::::::::::"""
|
||||
45:::""" @@@@@ """::::::::::::::::::""
|
||||
46::::::"" @@@@@"":::::::::::::::::::::::"""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
@@ -238,28 +239,28 @@ Alive entities: Player(hp=100, pos=(125.0, 244.5))
|
||||
53????????????????????????????????????????????????????????????????????????????????
|
||||
54????????????????????????????????????????????????????????????????????????????????
|
||||
55????????????????????????????????????????????????????????????????????????????????
|
||||
56????????????????????????????????????????????????????????????????????????????????
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,244.5) bodies=27/27 on_fire=false"
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||
Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(125.0, 244.5))
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||
|
||||
=== Tick 16 ===
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42 @@@@@
|
||||
43" @@@@@@ """""""""""""
|
||||
44:""" @@@@@@ "":::::::::::::"""
|
||||
45::::""" @@@@@ """::::::::::::::::::""
|
||||
46:::::::"" @@@@@ "":::::::::::::::::::::::"""
|
||||
47:::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::""""""""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
50????????????????????????????????????????????????????????????????????????????????
|
||||
@@ -268,28 +269,28 @@ Alive entities: Player(hp=100, pos=(125.0, 244.5))
|
||||
53????????????????????????????????????????????????????????????????????????????????
|
||||
54????????????????????????????????????????????????????????????????????????????????
|
||||
55????????????????????????????????????????????????????????????????????????????????
|
||||
56????????????????????????????????????????????????????????????????????????????????
|
||||
Player: "Player hp=100.0/100.0 pos=(124.5,244.5) bodies=27/27 on_fire=false"
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||
Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(124.5, 244.5))
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||
|
||||
=== Tick 18 ===
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42 @@@@@
|
||||
43" @@@@@@ """""""""""""
|
||||
44:""" @@@@@@ "":::::::::::::"""
|
||||
45::::""" @@@@@ """::::::::::::::::::""
|
||||
46:::::::"" @@@@@ "":::::::::::::::::::::::"""
|
||||
47:::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::""""""""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
50????????????????????????????????????????????????????????????????????????????????
|
||||
@@ -298,28 +299,28 @@ Alive entities: Player(hp=100, pos=(124.5, 244.5))
|
||||
53????????????????????????????????????????????????????????????????????????????????
|
||||
54????????????????????????????????????????????????????????????????????????????????
|
||||
55????????????????????????????????????????????????????????????????????????????????
|
||||
56????????????????????????????????????????????????????????????????????????????????
|
||||
Player: "Player hp=100.0/100.0 pos=(124.5,244.5) bodies=27/27 on_fire=false"
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||
Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(124.5, 244.5))
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||
|
||||
=== Tick 20 ===
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39
|
||||
40
|
||||
41
|
||||
42 @@@@@
|
||||
43" @@@@@@ """""""""""""
|
||||
44:""" @@@@@@ "":::::::::::::"""
|
||||
45::::""" @@@@@ """::::::::::::::::::""
|
||||
46:::::::"" @@@@@ "":::::::::::::::::::::::"""
|
||||
47:::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::""""""""
|
||||
37 TTTTT
|
||||
38 T T
|
||||
39 T T .
|
||||
40 T T ...
|
||||
41 T T ## @@@@@ .....
|
||||
42 T T ## @@@@@@ .......
|
||||
43 T T ## @@@@@@ """"""""""""" .........
|
||||
44""" T T ## @@@@@ "":::::::::::::""" ..........
|
||||
45:::""" ## @@@@@ """::::::::::::::::::"" ...........
|
||||
46::::::"" ## "":::::::::::::::::::::::""".........
|
||||
47::::::::"""""""""""""""""""""""""""""""""""::::::::::::::::::::::::::::"""""""""
|
||||
48::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
||||
49################################################################################
|
||||
50????????????????????????????????????????????????????????????????????????????????
|
||||
@@ -328,8 +329,7 @@ Alive entities: Player(hp=100, pos=(124.5, 244.5))
|
||||
53????????????????????????????????????????????????????????????????????????????????
|
||||
54????????????????????????????????????????????????????????????????????????????????
|
||||
55????????????????????????????????????????????????????????????????????????????????
|
||||
56????????????????????????????????????????????????????????????????????????????????
|
||||
Player: "Player hp=100.0/100.0 pos=(124.5,244.5) bodies=27/27 on_fire=false"
|
||||
Player: "Player hp=100.0/100.0 pos=(125.0,243.5) bodies=27/27 on_fire=false"
|
||||
Entities: 1
|
||||
Alive entities: Player(hp=100, pos=(124.5, 244.5))
|
||||
Alive entities: Player(hp=100, pos=(125.0, 243.5))
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ pub struct Entity {
|
||||
pub cy: f32,
|
||||
pub cvx: f32,
|
||||
pub cvy: f32,
|
||||
pub half_w: f32,
|
||||
pub half_h: f32,
|
||||
pub health: f32,
|
||||
pub max_health: f32,
|
||||
pub on_fire: bool,
|
||||
@@ -46,6 +48,8 @@ impl Entity {
|
||||
max_health: 100.0,
|
||||
on_fire: false,
|
||||
fire_timer: 0,
|
||||
half_w: 3.5,
|
||||
half_h: 3.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +110,8 @@ impl Entity {
|
||||
self.cy = cy;
|
||||
self.cvx = 0.0;
|
||||
self.cvy = 0.0;
|
||||
self.half_w = 3.5;
|
||||
self.half_h = 2.5;
|
||||
|
||||
let r = 0.5;
|
||||
let mat = match self.kind {
|
||||
|
||||
+235
-139
@@ -68,6 +68,79 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
// Water pool (left side)
|
||||
let water_x = 40;
|
||||
let water_surface = (h as i32 - 3) - ((water_x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let water_surface = water_surface.max(10).min(h as i32 - 3);
|
||||
for x in water_x - 12..=water_x + 12 {
|
||||
let s = (h as i32 - 3) - ((x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let s = s.max(10).min(h as i32 - 3);
|
||||
for y in s - 8..s {
|
||||
if self.grid.get(x as i32, y).is_empty() {
|
||||
self.grid.set_material(x as i32, y, MaterialId::Water);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lava pool (right side)
|
||||
let lava_x = 200;
|
||||
for x in lava_x - 10..=lava_x + 10 {
|
||||
let s = (h as i32 - 3) - ((x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let s = s.max(10).min(h as i32 - 3);
|
||||
for y in s - 5..s {
|
||||
if self.grid.get(x as i32, y).is_empty() {
|
||||
self.grid.set_material(x as i32, y, MaterialId::Lava);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wood structure near center-left
|
||||
let wood_x = 90;
|
||||
let wood_surface = (h as i32 - 3) - ((wood_x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let wood_surface = wood_surface.max(10).min(h as i32 - 3);
|
||||
for y in wood_surface - 8..wood_surface {
|
||||
self.grid.set_material(wood_x, y, MaterialId::Wood);
|
||||
self.grid.set_material(wood_x + 4, y, MaterialId::Wood);
|
||||
}
|
||||
for x in wood_x..=wood_x + 4 {
|
||||
self.grid.set_material(x, wood_surface - 8, MaterialId::Wood);
|
||||
}
|
||||
|
||||
// Sand dune (right of center)
|
||||
let sand_x = 160;
|
||||
let sand_surface = (h as i32 - 3) - ((sand_x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let sand_surface = sand_surface.max(10).min(h as i32 - 3);
|
||||
for dx in -8..=8 {
|
||||
let pile_h = (8.0 - (dx as f32).abs()) as i32;
|
||||
for dy in 0..pile_h {
|
||||
let y = sand_surface - 1 - dy;
|
||||
if self.grid.get(sand_x + dx, y).is_empty() {
|
||||
self.grid.set_material(sand_x + dx, y, MaterialId::Sand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acid pool (far left)
|
||||
let acid_x = 15;
|
||||
for x in acid_x - 5..=acid_x + 5 {
|
||||
let s = (h as i32 - 3) - ((x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let s = s.max(10).min(h as i32 - 3);
|
||||
for y in s - 4..s {
|
||||
if self.grid.get(x as i32, y).is_empty() {
|
||||
self.grid.set_material(x as i32, y, MaterialId::Acid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stone wall obstacle (between player and water)
|
||||
let wall_x = 110;
|
||||
let wall_surface = (h as i32 - 3) - ((wall_x as f32 * 0.1).sin() * 5.0) as i32;
|
||||
let wall_surface = wall_surface.max(10).min(h as i32 - 3);
|
||||
for y in wall_surface - 6..wall_surface {
|
||||
self.grid.set_material(wall_x, y, MaterialId::Stone);
|
||||
self.grid.set_material(wall_x + 1, y, MaterialId::Stone);
|
||||
}
|
||||
|
||||
self.grid.fill_border(MaterialId::Stone);
|
||||
|
||||
let cx = (w / 2) as f32;
|
||||
@@ -159,14 +232,11 @@ impl Game {
|
||||
|
||||
pub fn check_on_ground(&self) -> bool {
|
||||
if let Some(e) = self.player.entity(&self.entities) {
|
||||
for b in &e.bodies {
|
||||
if !b.alive {
|
||||
continue;
|
||||
}
|
||||
let bx = b.x as i32;
|
||||
let by = b.y as i32;
|
||||
let below = self.grid.get(bx, by + 1);
|
||||
if below.is_solid() {
|
||||
let bottom = (e.cy + e.half_h + 0.1) as i32;
|
||||
let left = (e.cx - e.half_w) as i32;
|
||||
let right = (e.cx + e.half_w) as i32;
|
||||
for x in left..=right {
|
||||
if self.grid.in_bounds(x, bottom) && self.grid.get(x, bottom).is_solid() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -232,147 +302,76 @@ impl Game {
|
||||
}
|
||||
|
||||
fn update_rigid_entity(&mut self, idx: usize, gravity: f32, damping: f32, max_vel: f32) {
|
||||
let grid = &self.grid;
|
||||
let (cx, cy, cvx, cvy) = {
|
||||
let (cx, cy, cvx, cvy, half_w, half_h) = {
|
||||
let e = &self.entities.all()[idx];
|
||||
(e.cx, e.cy, e.cvx, e.cvy)
|
||||
(e.cx, e.cy, e.cvx, e.cvy, e.half_w, e.half_h)
|
||||
};
|
||||
|
||||
let mut new_cx = cx;
|
||||
let mut new_cy = cy;
|
||||
let mut new_cvx = cvx * damping;
|
||||
let mut new_cvy = cvy * damping;
|
||||
let mut nx = cx;
|
||||
let mut ny = cy;
|
||||
let mut nvx = cvx * damping;
|
||||
let mut nvy = cvy * damping;
|
||||
nvy += gravity;
|
||||
|
||||
new_cvy += gravity;
|
||||
|
||||
let v_mag = (new_cvx * new_cvx + new_cvy * new_cvy).sqrt();
|
||||
let v_mag = (nvx * nvx + nvy * nvy).sqrt();
|
||||
if v_mag > max_vel {
|
||||
new_cvx = new_cvx / v_mag * max_vel;
|
||||
new_cvy = new_cvy / v_mag * max_vel;
|
||||
nvx = nvx / v_mag * max_vel;
|
||||
nvy = nvy / v_mag * max_vel;
|
||||
}
|
||||
|
||||
new_cx += new_cvx;
|
||||
new_cy += new_cvy;
|
||||
// Move X then resolve X collisions
|
||||
nx += nvx;
|
||||
let (resolved_x, hit_wall_x) = self.resolve_aabb_x(idx, nx, ny, half_w, half_h, nvx > 0.0);
|
||||
nx = resolved_x;
|
||||
if hit_wall_x {
|
||||
if nvx > 0.0 { nvx = 0.0; }
|
||||
else if nvx < 0.0 { nvx = 0.0; }
|
||||
}
|
||||
|
||||
let offsets = self.entities.all()[idx].rest_offsets.clone();
|
||||
let radii: Vec<f32> = self.entities.all()[idx].bodies.iter().map(|b| b.radius).collect();
|
||||
// Move Y then resolve Y collisions
|
||||
ny += nvy;
|
||||
let (resolved_y, hit_floor, hit_ceiling) = self.resolve_aabb_y(idx, nx, ny, half_w, half_h, nvy > 0.0);
|
||||
ny = resolved_y;
|
||||
if hit_floor {
|
||||
nvy = 0.0;
|
||||
}
|
||||
if hit_ceiling {
|
||||
nvy = 0.0;
|
||||
}
|
||||
|
||||
for substep in 0..4 {
|
||||
let mut total_push_x = 0.0;
|
||||
let mut total_push_y = 0.0;
|
||||
let mut push_count = 0;
|
||||
|
||||
for (i, &(ox, oy)) in offsets.iter().enumerate() {
|
||||
let bx = new_cx + ox;
|
||||
let by = new_cy + oy;
|
||||
let r = radii[i];
|
||||
|
||||
let min_x = (bx - r).floor() as i32;
|
||||
let max_x = (bx + r).ceil() as i32;
|
||||
let min_y = (by - r).floor() as i32;
|
||||
let max_y = (by + r).ceil() as i32;
|
||||
|
||||
for cy_cell in min_y..=max_y {
|
||||
for cx_cell in min_x..=max_x {
|
||||
if !grid.in_bounds(cx_cell, cy_cell) {
|
||||
continue;
|
||||
}
|
||||
let cell = grid.get(cx_cell, cy_cell);
|
||||
if cell.is_empty() || cell.is_liquid() {
|
||||
continue;
|
||||
}
|
||||
if cell.material == MaterialId::Fire {
|
||||
continue;
|
||||
}
|
||||
if !cell.is_solid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell_min_x = cx_cell as f32;
|
||||
let cell_max_x = (cx_cell + 1) as f32;
|
||||
let cell_min_y = cy_cell as f32;
|
||||
let cell_max_y = (cy_cell + 1) as f32;
|
||||
|
||||
let inside_x = bx >= cell_min_x && bx < cell_max_x;
|
||||
let inside_y = by >= cell_min_y && by < cell_max_y;
|
||||
|
||||
if inside_x && inside_y {
|
||||
let dl = bx - cell_min_x;
|
||||
let dr = cell_max_x - bx;
|
||||
let dt = by - cell_min_y;
|
||||
let db = cell_max_y - by;
|
||||
let md = dl.min(dr).min(dt).min(db);
|
||||
if md == dt {
|
||||
total_push_y -= r + md;
|
||||
push_count += 1;
|
||||
if new_cvy > 0.0 { new_cvy = 0.0; }
|
||||
} else if md == db {
|
||||
total_push_y += r + md;
|
||||
push_count += 1;
|
||||
if new_cvy < 0.0 { new_cvy = 0.0; }
|
||||
} else if md == dl {
|
||||
total_push_x -= r + md;
|
||||
push_count += 1;
|
||||
if new_cvx > 0.0 { new_cvx = 0.0; }
|
||||
} else {
|
||||
total_push_x += r + md;
|
||||
push_count += 1;
|
||||
if new_cvx < 0.0 { new_cvx = 0.0; }
|
||||
}
|
||||
} else {
|
||||
let closest_x = bx.max(cell_min_x).min(cell_max_x);
|
||||
let closest_y = by.max(cell_min_y).min(cell_max_y);
|
||||
let dx = bx - closest_x;
|
||||
let dy = by - 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;
|
||||
total_push_x += dx / dist * overlap;
|
||||
total_push_y += dy / dist * overlap;
|
||||
push_count += 1;
|
||||
if dy < -0.5 && new_cvy > 0.0 { new_cvy = 0.0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check material contacts
|
||||
let (touching_lava, touching_fire, touching_acid, in_liquid) = {
|
||||
let grid = &self.grid;
|
||||
let mut tl = false;
|
||||
let mut tf = false;
|
||||
let mut ta = false;
|
||||
let mut il = false;
|
||||
let min_x = (nx - half_w).floor() as i32;
|
||||
let max_x = (nx + half_w).ceil() as i32;
|
||||
let min_y = (ny - half_h).floor() as i32;
|
||||
let max_y = (ny + half_h).ceil() as i32;
|
||||
for y in min_y..=max_y {
|
||||
for x in min_x..=max_x {
|
||||
if !grid.in_bounds(x, y) { continue; }
|
||||
let cell = grid.get(x, y);
|
||||
if cell.material == MaterialId::Lava { tl = true; }
|
||||
if cell.material == MaterialId::Fire { tf = true; }
|
||||
if cell.material == MaterialId::Acid { ta = true; }
|
||||
if cell.is_liquid() { il = true; }
|
||||
}
|
||||
}
|
||||
|
||||
if push_count > 0 {
|
||||
let inv = 1.0 / push_count as f32;
|
||||
let px = total_push_x * inv;
|
||||
let py = total_push_y * inv;
|
||||
let mag = (px * px + py * py).sqrt();
|
||||
if mag > 0.001 {
|
||||
new_cx += px;
|
||||
new_cy += py;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut touching_lava = false;
|
||||
let mut touching_fire = false;
|
||||
let mut touching_acid = false;
|
||||
let mut in_liquid = false;
|
||||
|
||||
for (i, &(ox, oy)) in offsets.iter().enumerate() {
|
||||
let bx = (new_cx + ox) as i32;
|
||||
let by = (new_cy + oy) as i32;
|
||||
if !grid.in_bounds(bx, by) {
|
||||
continue;
|
||||
}
|
||||
let cell = grid.get(bx, by);
|
||||
if cell.material == MaterialId::Lava { touching_lava = true; }
|
||||
if cell.material == MaterialId::Fire { touching_fire = true; }
|
||||
if cell.material == MaterialId::Acid { touching_acid = true; }
|
||||
if cell.is_liquid() { in_liquid = true; }
|
||||
}
|
||||
(tl, tf, ta, il)
|
||||
};
|
||||
|
||||
if let Some(e) = self.entities.all_mut().get_mut(idx) {
|
||||
e.cx = new_cx;
|
||||
e.cy = new_cy;
|
||||
e.cvx = new_cvx;
|
||||
e.cvy = new_cvy;
|
||||
e.cx = nx;
|
||||
e.cy = ny;
|
||||
e.cvx = nvx;
|
||||
e.cvy = nvy;
|
||||
if in_liquid {
|
||||
e.cvy *= 0.6;
|
||||
e.cvx *= 0.8;
|
||||
}
|
||||
e.sync_bodies_to_center();
|
||||
|
||||
if touching_lava {
|
||||
@@ -396,11 +395,108 @@ impl Game {
|
||||
if b.alive { b.health -= 0.25; }
|
||||
}
|
||||
}
|
||||
if in_liquid {
|
||||
new_cvy *= 0.5;
|
||||
e.cvy = new_cvy;
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_aabb_x(&self, _idx: usize, cx: f32, cy: f32, hw: f32, hh: f32, _moving_right: bool) -> (f32, bool) {
|
||||
let grid = &self.grid;
|
||||
let left = cx - hw;
|
||||
let right = cx + hw;
|
||||
let top = cy - hh;
|
||||
let bottom = cy + hh;
|
||||
|
||||
let min_x = left.floor() as i32;
|
||||
let max_x = right.ceil() as i32;
|
||||
let min_y = top.floor() as i32;
|
||||
let max_y = bottom.ceil() as i32;
|
||||
|
||||
let mut best_push = 0.0f32;
|
||||
let mut hit = false;
|
||||
|
||||
for y in min_y..=max_y {
|
||||
for x in min_x..=max_x {
|
||||
if !grid.in_bounds(x, y) { continue; }
|
||||
let cell = grid.get(x, y);
|
||||
if !cell.is_solid() { continue; }
|
||||
|
||||
let cell_left = x as f32;
|
||||
let cell_right = (x + 1) as f32;
|
||||
let cell_top = y as f32;
|
||||
let cell_bottom = (y + 1) as f32;
|
||||
|
||||
if bottom <= cell_top || top >= cell_bottom {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pen_left = right - cell_left;
|
||||
let pen_right = cell_right - left;
|
||||
|
||||
let push = if pen_left < pen_right { -pen_left } else { pen_right };
|
||||
if push.abs() > best_push.abs() {
|
||||
best_push = push;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(cx + best_push, hit)
|
||||
}
|
||||
|
||||
fn resolve_aabb_y(&self, _idx: usize, cx: f32, cy: f32, hw: f32, hh: f32, moving_down: bool) -> (f32, bool, bool) {
|
||||
let grid = &self.grid;
|
||||
let left = cx - hw;
|
||||
let right = cx + hw;
|
||||
let top = cy - hh;
|
||||
let bottom = cy + hh;
|
||||
|
||||
let min_x = left.floor() as i32;
|
||||
let max_x = right.ceil() as i32;
|
||||
let min_y = top.floor() as i32;
|
||||
let max_y = bottom.ceil() as i32;
|
||||
|
||||
let mut max_pen = 0.0f32;
|
||||
let mut hit_floor = false;
|
||||
let mut hit_ceiling = false;
|
||||
|
||||
for y in min_y..=max_y {
|
||||
for x in min_x..=max_x {
|
||||
if !grid.in_bounds(x, y) { continue; }
|
||||
let cell = grid.get(x, y);
|
||||
if !cell.is_solid() { continue; }
|
||||
|
||||
let cell_left = x as f32;
|
||||
let cell_right = (x + 1) as f32;
|
||||
let cell_top = y as f32;
|
||||
let cell_bottom = (y + 1) as f32;
|
||||
|
||||
if right <= cell_left || left >= cell_right {
|
||||
continue;
|
||||
}
|
||||
|
||||
if moving_down {
|
||||
let pen = bottom - cell_top;
|
||||
if pen > max_pen {
|
||||
max_pen = pen;
|
||||
hit_floor = true;
|
||||
}
|
||||
} else {
|
||||
let pen = cell_bottom - top;
|
||||
if pen > max_pen {
|
||||
max_pen = pen;
|
||||
hit_ceiling = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let new_cy = if hit_floor {
|
||||
cy - max_pen
|
||||
} else if hit_ceiling {
|
||||
cy + max_pen
|
||||
} else {
|
||||
cy
|
||||
};
|
||||
(new_cy, hit_floor, hit_ceiling)
|
||||
}
|
||||
|
||||
fn update_ragdoll_entity(&mut self, idx: usize, solver: &crate::physics::verlet::VerletSolver, substeps: u32) {
|
||||
|
||||
@@ -13,17 +13,17 @@ fn setup_empty() -> GameSession {
|
||||
fn player_falls_and_lands() {
|
||||
let mut s = GameSession::new_seeded(42);
|
||||
s.init_empty();
|
||||
s.clear_area(115, 120, 20, 10);
|
||||
s.clear_area(115, 115, 20, 15);
|
||||
s.perform_action(&AiAction::FillRect { x: 110, y: 130, w: 30, h: 15, material: "stone".into() });
|
||||
s.step(40);
|
||||
s.step(80);
|
||||
let player = s.get_player().expect("player should exist");
|
||||
assert!(player.alive, "player should be alive");
|
||||
let y = player.pos[1];
|
||||
assert!(y < 140.0, "player should not fall through stone floor, got y={}", y);
|
||||
s.step(20);
|
||||
assert!(y < 135.0, "player should not fall through stone floor, got y={}", y);
|
||||
s.step(30);
|
||||
let player2 = s.get_player().expect("player should exist");
|
||||
let dy = (player2.pos[1] - y).abs();
|
||||
assert!(dy < 2.0, "player should have stopped falling (dy={:.2}), y={} -> {}", dy, y, player2.pos[1]);
|
||||
assert!(dy < 3.0, "player should have stopped falling (dy={:.2}), y={} -> {}", dy, y, player2.pos[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user