refactor(physics): defer static collision responses
Split type-one and type-two detection from response resolution so the retained candidate can be applied to the motion state that exists after the complete record scan. Existing response functions remain compatibility wrappers, and a focused test proves late motion changes participate in resolution. Test Plan: - cargo test --all-targets - cargo clippy --all-targets --all-features -- -D warnings - git diff --check
This commit is contained in:
@@ -24,6 +24,15 @@ pub struct CollisionResponse {
|
||||
pub auxiliary_fired: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StaticCollisionCandidate {
|
||||
pub surface_distance: i32,
|
||||
normal_x: Real48,
|
||||
normal_y: Real48,
|
||||
normal_velocity: i32,
|
||||
material: CollisionMaterial,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CollisionMaterial {
|
||||
pub normal_rebound: f64,
|
||||
@@ -87,6 +96,25 @@ impl CollisionMaterial {
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticCollisionCandidate {
|
||||
pub fn resolve(self, velocity: MilliVec, spin: Real48) -> CollisionResponse {
|
||||
let (velocity, spin, auxiliary_fired) = apply_response(
|
||||
velocity,
|
||||
spin,
|
||||
self.normal_x,
|
||||
self.normal_y,
|
||||
self.normal_velocity,
|
||||
self.material.real48(),
|
||||
);
|
||||
CollisionResponse {
|
||||
surface_distance: self.surface_distance,
|
||||
velocity,
|
||||
spin,
|
||||
auxiliary_fired,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ResponseCoefficients {
|
||||
normal: Real48,
|
||||
@@ -293,14 +321,13 @@ fn apply_response(
|
||||
}
|
||||
|
||||
/// Calculate the original type-2 response in the registered segment's basis.
|
||||
pub fn line_collision_response(
|
||||
pub fn line_collision_candidate(
|
||||
old_position: MilliVec,
|
||||
velocity: MilliVec,
|
||||
line_start: Vec2,
|
||||
line_end: Vec2,
|
||||
material: CollisionMaterial,
|
||||
spin: Real48,
|
||||
) -> Option<CollisionResponse> {
|
||||
) -> Option<StaticCollisionCandidate> {
|
||||
let start = MilliVec::from_position(line_start);
|
||||
let end = MilliVec::from_position(line_end);
|
||||
let predicted = old_position.add(velocity);
|
||||
@@ -337,22 +364,27 @@ pub fn line_collision_response(
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let (velocity, spin, auxiliary_fired) = apply_response(
|
||||
velocity,
|
||||
spin,
|
||||
Some(StaticCollisionCandidate {
|
||||
surface_distance: distance,
|
||||
normal_x,
|
||||
normal_y,
|
||||
collision_velocity,
|
||||
material.real48(),
|
||||
);
|
||||
Some(CollisionResponse {
|
||||
surface_distance: distance,
|
||||
velocity,
|
||||
spin,
|
||||
auxiliary_fired,
|
||||
normal_velocity: collision_velocity,
|
||||
material,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn line_collision_response(
|
||||
old_position: MilliVec,
|
||||
velocity: MilliVec,
|
||||
start: Vec2,
|
||||
end: Vec2,
|
||||
material: CollisionMaterial,
|
||||
spin: Real48,
|
||||
) -> Option<CollisionResponse> {
|
||||
line_collision_candidate(old_position, velocity, start, end, material)
|
||||
.map(|candidate| candidate.resolve(velocity, spin))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn collide_with_line(
|
||||
old_position: MilliVec,
|
||||
@@ -377,14 +409,13 @@ pub fn collide_with_line(
|
||||
}
|
||||
|
||||
/// Calculate the original type-1 circle response for a path entering it.
|
||||
pub fn circle_collision_response(
|
||||
pub fn circle_collision_candidate(
|
||||
old_position: MilliVec,
|
||||
velocity: MilliVec,
|
||||
center: Vec2,
|
||||
radius: f32,
|
||||
material: CollisionMaterial,
|
||||
spin: Real48,
|
||||
) -> Option<CollisionResponse> {
|
||||
) -> Option<StaticCollisionCandidate> {
|
||||
let center = MilliVec::from_position(center);
|
||||
let radius_milli = (radius * 1_000.0).round() as i32;
|
||||
let mut surface_distance =
|
||||
@@ -411,22 +442,27 @@ pub fn circle_collision_response(
|
||||
return None;
|
||||
}
|
||||
surface_distance = surface_distance.wrapping_add(3_000);
|
||||
let (velocity, spin, auxiliary_fired) = apply_response(
|
||||
velocity,
|
||||
spin,
|
||||
Some(StaticCollisionCandidate {
|
||||
surface_distance,
|
||||
normal_x,
|
||||
normal_y,
|
||||
collision_velocity,
|
||||
material.real48(),
|
||||
);
|
||||
Some(CollisionResponse {
|
||||
surface_distance,
|
||||
velocity,
|
||||
spin,
|
||||
auxiliary_fired,
|
||||
normal_velocity: collision_velocity,
|
||||
material,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn circle_collision_response(
|
||||
old_position: MilliVec,
|
||||
velocity: MilliVec,
|
||||
center: Vec2,
|
||||
radius: f32,
|
||||
material: CollisionMaterial,
|
||||
spin: Real48,
|
||||
) -> Option<CollisionResponse> {
|
||||
circle_collision_candidate(old_position, velocity, center, radius, material)
|
||||
.map(|candidate| candidate.resolve(velocity, spin))
|
||||
}
|
||||
|
||||
pub fn ball_collision_response(
|
||||
old_position: MilliVec,
|
||||
velocity: MilliVec,
|
||||
@@ -562,6 +598,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detected_candidate_resolves_against_the_later_motion_state() {
|
||||
let old = MilliVec {
|
||||
x: 326_000,
|
||||
y: 200_000,
|
||||
};
|
||||
let detected_velocity = MilliVec { x: 3_000, y: 0 };
|
||||
let candidate = line_collision_candidate(
|
||||
old,
|
||||
detected_velocity,
|
||||
vec2(328.0, 422.0),
|
||||
vec2(328.0, 58.0),
|
||||
CollisionMaterial::line(0.6, 0.1),
|
||||
)
|
||||
.expect("the candidate must be retained during the record scan");
|
||||
|
||||
let response = candidate.resolve(MilliVec { x: 3_000, y: 1_000 }, Real48::ZERO);
|
||||
|
||||
assert_eq!(response.velocity, MilliVec { x: -1_800, y: 1_000 });
|
||||
assert_eq!(response.surface_distance, 2_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auxiliary_kick_uses_the_recovered_negative_speed_threshold() {
|
||||
let material = CollisionMaterial::line_with_kick(0.5, 0.1, -0.4, 0.4);
|
||||
|
||||
Reference in New Issue
Block a user