block slope sliding
This commit is contained in:
+91
-4
@@ -38,7 +38,11 @@ const PITCH_LIMIT: f32 = 1.55;
|
|||||||
/// Halbmaße der Spieler-AABB (units): 0.6 m breit/tief, 1.8 m hoch.
|
/// Halbmaße der Spieler-AABB (units): 0.6 m breit/tief, 1.8 m hoch.
|
||||||
/// Öffentlich, weil die Session dieselbe Box gegen Trigger-Zonen prüft.
|
/// Öffentlich, weil die Session dieselbe Box gegen Trigger-Zonen prüft.
|
||||||
pub const HALF_EXTENTS: [f32; 3] = [0.3, 0.9, 0.3];
|
pub const HALF_EXTENTS: [f32; 3] = [0.3, 0.9, 0.3];
|
||||||
/// Trefferflächen mit Normalen-Y darüber gelten als Boden (~45°-Rampen ok).
|
/// Grenzwinkel für begehbaren Boden, als Kosinus (= Y-Komponente der
|
||||||
|
/// Trefferormale einer Fläche mit diesem Neigungswinkel). 0.7 ≈ **45°**:
|
||||||
|
/// flacher wird gelaufen, steiler wird gerutscht. Das ist *der* Regler fürs
|
||||||
|
/// Hangverhalten — Wunschwinkel als Kosinus eintragen (30° → 0.866,
|
||||||
|
/// 50° → 0.643, 60° → 0.5).
|
||||||
const GROUND_NORMAL_Y: f32 = 0.7;
|
const GROUND_NORMAL_Y: f32 = 0.7;
|
||||||
/// Slide-Iterationen pro Schritt (Wände, Ecken, Boden zugleich).
|
/// Slide-Iterationen pro Schritt (Wände, Ecken, Boden zugleich).
|
||||||
const MAX_SLIDES: u32 = 4;
|
const MAX_SLIDES: u32 = 4;
|
||||||
@@ -103,14 +107,26 @@ impl Player {
|
|||||||
self.vel[0] = (f[0] * fwd + r[0] * right) * WALK_SPEED;
|
self.vel[0] = (f[0] * fwd + r[0] * right) * WALK_SPEED;
|
||||||
self.vel[2] = (f[1] * fwd + r[1] * right) * WALK_SPEED;
|
self.vel[2] = (f[1] * fwd + r[1] * right) * WALK_SPEED;
|
||||||
|
|
||||||
// Sprung vor der Gravitation, nur vom Boden (Vorframe-`grounded`) —
|
// Sprung nur vom Boden (Vorframe-`grounded`) — kein Doppelsprung.
|
||||||
// kein Doppelsprung.
|
//
|
||||||
|
// Gravitation wirkt *nur in der Luft*. Am Boden wird die Vertikal-
|
||||||
|
// geschwindigkeit stattdessen genullt, denn `slide` projiziert sie auf
|
||||||
|
// die Trefferebene und lässt auf einer Schräge den hangparallelen Rest
|
||||||
|
// stehen — Hangabtrieb, der sich über die Frames aufschaukelt und den
|
||||||
|
// Spieler auch sanfte Rampen hinabkriechen lässt. Ohne vertikale
|
||||||
|
// Geschwindigkeit gibt es nichts zu projizieren, er bleibt stehen; ans
|
||||||
|
// Gefälle heftet ihn der Ground-Snap unten. Steiler als
|
||||||
|
// [`GROUND_NORMAL_Y`] wird `grounded` gar nicht erst gesetzt, dort
|
||||||
|
// greift der Luft-Zweig und der Spieler rutscht wie gewünscht ab.
|
||||||
let jumped = jump && was_grounded;
|
let jumped = jump && was_grounded;
|
||||||
if jumped {
|
if jumped {
|
||||||
self.vel[1] = JUMP_SPEED;
|
self.vel[1] = JUMP_SPEED;
|
||||||
self.grounded = false;
|
self.grounded = false;
|
||||||
}
|
} else if was_grounded {
|
||||||
|
self.vel[1] = 0.0;
|
||||||
|
} else {
|
||||||
self.vel[1] -= GRAVITY * dt;
|
self.vel[1] -= GRAVITY * dt;
|
||||||
|
}
|
||||||
|
|
||||||
self.move_and_slide(world, dt);
|
self.move_and_slide(world, dt);
|
||||||
|
|
||||||
@@ -462,6 +478,77 @@ mod tests {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keilförmige Rampe, die in +X mit `deg` Grad ansteigt (Fuß bei x=0,
|
||||||
|
/// Länge 10). Bergab ist damit -X.
|
||||||
|
fn slope_world(deg: f32) -> CollisionWorld {
|
||||||
|
let (s, c) = deg.to_radians().sin_cos();
|
||||||
|
collision::from_brushes(vec![vec![
|
||||||
|
collision::Plane { n: [-s, c, 0.0], d: 0.0 }, // Rampenoberfläche
|
||||||
|
collision::Plane { n: [0.0, -1.0, 0.0], d: 0.0 }, // Unterseite (y≥0)
|
||||||
|
collision::Plane { n: [1.0, 0.0, 0.0], d: 10.0 }, // x≤10
|
||||||
|
collision::Plane { n: [-1.0, 0.0, 0.0], d: 0.0 }, // x≥0
|
||||||
|
collision::Plane { n: [0.0, 0.0, 1.0], d: 50.0 },
|
||||||
|
collision::Plane { n: [0.0, 0.0, -1.0], d: 50.0 },
|
||||||
|
]])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auf eine Rampe fallen lassen und setzen — gibt den Ruhepunkt zurück.
|
||||||
|
fn settle(w: &CollisionWorld, x: f32, deg: f32) -> Player {
|
||||||
|
// Über der Fläche starten — mit Abstand, sonst steckt die aufgeblasene
|
||||||
|
// Box schon in der Rampe und die Trace lässt sie durchfallen.
|
||||||
|
let y = x * deg.to_radians().tan() + 1.0;
|
||||||
|
let mut p = Player::new([x, y, 0.0]);
|
||||||
|
for _ in 0..60 { p.step(w, 0.0, 0.0, false, 1.0 / 60.0); }
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stands_still_on_walkable_slope() {
|
||||||
|
// 30° < Grenzwinkel: ohne Eingabe darf der Spieler nicht abwärts
|
||||||
|
// kriechen — sonst ist der Hangabtrieb zurück.
|
||||||
|
let w = slope_world(30.0);
|
||||||
|
let mut p = settle(&w, 5.0, 30.0);
|
||||||
|
assert!(p.grounded, "sollte auf der 30°-Rampe stehen: {:?}", p.pos);
|
||||||
|
let (x0, y0) = (p.pos[0], p.pos[1]);
|
||||||
|
for _ in 0..300 { p.step(&w, 0.0, 0.0, false, 1.0 / 60.0); } // 5 Sekunden
|
||||||
|
assert!((p.pos[0] - x0).abs() < 0.01, "kein Hangabtrieb: x {x0} -> {}", p.pos[0]);
|
||||||
|
assert!((p.pos[1] - y0).abs() < 0.01, "keine Höhendrift: y {y0} -> {}", p.pos[1]);
|
||||||
|
assert!(p.grounded, "bleibt am Boden");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn slides_down_steep_slope() {
|
||||||
|
// 60° > Grenzwinkel: hier soll er abrutschen (bergab = -X).
|
||||||
|
let w = slope_world(60.0);
|
||||||
|
let mut p = settle(&w, 5.0, 60.0);
|
||||||
|
assert!(!p.grounded, "zu steil zum Stehen: {:?}", p.pos);
|
||||||
|
let (x0, y0) = (p.pos[0], p.pos[1]);
|
||||||
|
for _ in 0..60 { p.step(&w, 0.0, 0.0, false, 1.0 / 60.0); }
|
||||||
|
assert!(p.pos[0] < x0 - 0.5, "sollte hangabwärts rutschen: x {x0} -> {}", p.pos[0]);
|
||||||
|
assert!(p.pos[1] < y0 - 0.5, "und dabei sinken: y {y0} -> {}", p.pos[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn walks_up_and_down_walkable_slope() {
|
||||||
|
// Der stehenbleibende Spieler muss trotzdem beide Richtungen laufen.
|
||||||
|
let w = slope_world(30.0);
|
||||||
|
let mut up = settle(&w, 5.0, 30.0);
|
||||||
|
up.yaw = -std::f32::consts::FRAC_PI_2; // vorwärts → +X, bergauf
|
||||||
|
let y0 = up.pos[1];
|
||||||
|
for _ in 0..60 { up.step(&w, 1.0, 0.0, false, 1.0 / 60.0); }
|
||||||
|
assert!(up.pos[1] > y0 + 0.5, "bergauf steigt y: {y0} -> {}", up.pos[1]);
|
||||||
|
assert!(up.grounded, "bleibt bergauf am Boden");
|
||||||
|
|
||||||
|
let mut down = settle(&w, 5.0, 30.0);
|
||||||
|
down.yaw = std::f32::consts::FRAC_PI_2; // vorwärts → -X, bergab
|
||||||
|
let y0 = down.pos[1];
|
||||||
|
for i in 0..60 {
|
||||||
|
down.step(&w, 1.0, 0.0, false, 1.0 / 60.0);
|
||||||
|
assert!(down.grounded, "bergab am Boden kleben (i={i}, {:?})", down.pos);
|
||||||
|
}
|
||||||
|
assert!(down.pos[1] < y0 - 0.5, "bergab sinkt y: {y0} -> {}", down.pos[1]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walks_down_slope_across_seams_at_60fps() {
|
fn walks_down_slope_across_seams_at_60fps() {
|
||||||
let w = slope_between_floors();
|
let w = slope_between_floors();
|
||||||
|
|||||||
Reference in New Issue
Block a user