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,
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct CollisionMaterial {
|
pub struct CollisionMaterial {
|
||||||
pub normal_rebound: f64,
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
struct ResponseCoefficients {
|
struct ResponseCoefficients {
|
||||||
normal: Real48,
|
normal: Real48,
|
||||||
@@ -293,14 +321,13 @@ fn apply_response(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the original type-2 response in the registered segment's basis.
|
/// 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,
|
old_position: MilliVec,
|
||||||
velocity: MilliVec,
|
velocity: MilliVec,
|
||||||
line_start: Vec2,
|
line_start: Vec2,
|
||||||
line_end: Vec2,
|
line_end: Vec2,
|
||||||
material: CollisionMaterial,
|
material: CollisionMaterial,
|
||||||
spin: Real48,
|
) -> Option<StaticCollisionCandidate> {
|
||||||
) -> Option<CollisionResponse> {
|
|
||||||
let start = MilliVec::from_position(line_start);
|
let start = MilliVec::from_position(line_start);
|
||||||
let end = MilliVec::from_position(line_end);
|
let end = MilliVec::from_position(line_end);
|
||||||
let predicted = old_position.add(velocity);
|
let predicted = old_position.add(velocity);
|
||||||
@@ -337,22 +364,27 @@ pub fn line_collision_response(
|
|||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (velocity, spin, auxiliary_fired) = apply_response(
|
Some(StaticCollisionCandidate {
|
||||||
velocity,
|
surface_distance: distance,
|
||||||
spin,
|
|
||||||
normal_x,
|
normal_x,
|
||||||
normal_y,
|
normal_y,
|
||||||
collision_velocity,
|
normal_velocity: collision_velocity,
|
||||||
material.real48(),
|
material,
|
||||||
);
|
|
||||||
Some(CollisionResponse {
|
|
||||||
surface_distance: distance,
|
|
||||||
velocity,
|
|
||||||
spin,
|
|
||||||
auxiliary_fired,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
pub fn collide_with_line(
|
pub fn collide_with_line(
|
||||||
old_position: MilliVec,
|
old_position: MilliVec,
|
||||||
@@ -377,14 +409,13 @@ pub fn collide_with_line(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the original type-1 circle response for a path entering it.
|
/// 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,
|
old_position: MilliVec,
|
||||||
velocity: MilliVec,
|
velocity: MilliVec,
|
||||||
center: Vec2,
|
center: Vec2,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
material: CollisionMaterial,
|
material: CollisionMaterial,
|
||||||
spin: Real48,
|
) -> Option<StaticCollisionCandidate> {
|
||||||
) -> Option<CollisionResponse> {
|
|
||||||
let center = MilliVec::from_position(center);
|
let center = MilliVec::from_position(center);
|
||||||
let radius_milli = (radius * 1_000.0).round() as i32;
|
let radius_milli = (radius * 1_000.0).round() as i32;
|
||||||
let mut surface_distance =
|
let mut surface_distance =
|
||||||
@@ -411,22 +442,27 @@ pub fn circle_collision_response(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
surface_distance = surface_distance.wrapping_add(3_000);
|
surface_distance = surface_distance.wrapping_add(3_000);
|
||||||
let (velocity, spin, auxiliary_fired) = apply_response(
|
Some(StaticCollisionCandidate {
|
||||||
velocity,
|
surface_distance,
|
||||||
spin,
|
|
||||||
normal_x,
|
normal_x,
|
||||||
normal_y,
|
normal_y,
|
||||||
collision_velocity,
|
normal_velocity: collision_velocity,
|
||||||
material.real48(),
|
material,
|
||||||
);
|
|
||||||
Some(CollisionResponse {
|
|
||||||
surface_distance,
|
|
||||||
velocity,
|
|
||||||
spin,
|
|
||||||
auxiliary_fired,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
pub fn ball_collision_response(
|
||||||
old_position: MilliVec,
|
old_position: MilliVec,
|
||||||
velocity: 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]
|
#[test]
|
||||||
fn auxiliary_kick_uses_the_recovered_negative_speed_threshold() {
|
fn auxiliary_kick_uses_the_recovered_negative_speed_threshold() {
|
||||||
let material = CollisionMaterial::line_with_kick(0.5, 0.1, -0.4, 0.4);
|
let material = CollisionMaterial::line_with_kick(0.5, 0.1, -0.4, 0.4);
|
||||||
|
|||||||
Reference in New Issue
Block a user