fix(physics): resolve the earliest substep contact
Return exact path progress with each fixed-point line and circle candidate. Evaluate the complete recovered physical object set and apply the nearest contact, matching the selection logic in the original collision dispatcher instead of stopping at the lowest matching object id. This prevents dense assemblies from choosing a later wall or circle merely because its record appears earlier in the table. Test Plan: - `cargo test --all-targets` -- 45 passed - `cargo clippy --all-targets -- -D warnings` -- passed - `cargo build --profile production` -- passed - collision-progress ordering test -- passed - `git diff --cached --check` -- passed
This commit is contained in:
+28
-24
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
geometry::{Segment, closest_point},
|
||||
original_physics::{
|
||||
GRAVITY_MILLI_PER_STEP, MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec, STEP_SECONDS,
|
||||
collide_with_circle, collide_with_line, path_intersects_circle,
|
||||
CollisionResponse, GRAVITY_MILLI_PER_STEP, MAXIMUM_SPEED_MILLI_PER_STEP, MilliVec,
|
||||
STEP_SECONDS, circle_collision_response, line_collision_response, path_intersects_circle,
|
||||
},
|
||||
table::{BUMPERS, PASSIVE_CIRCLES, TARGET_SENSORS, WALLS},
|
||||
};
|
||||
@@ -405,53 +405,57 @@ impl Game {
|
||||
velocity.clamp_speed(MAXIMUM_SPEED_MILLI_PER_STEP);
|
||||
let movement_velocity = velocity;
|
||||
let mut position = old_position.add(velocity);
|
||||
let mut hit_wall = None;
|
||||
let mut hit_circle = None;
|
||||
let mut best_collision: Option<(u8, bool, CollisionResponse)> = None;
|
||||
for object_id in 1..=175 {
|
||||
if self.claw.active && (12..=20).contains(&object_id) {
|
||||
continue;
|
||||
}
|
||||
if let Some(wall) = WALLS.iter().find(|wall| wall.id == object_id) {
|
||||
let segment = self.live_wall_segment(wall.id, wall.segment);
|
||||
let mut response = velocity;
|
||||
if collide_with_line(
|
||||
if let Some(response) = line_collision_response(
|
||||
old_position,
|
||||
&mut response,
|
||||
velocity,
|
||||
segment.start,
|
||||
segment.end,
|
||||
f64::from(wall.normal_rebound),
|
||||
f64::from(wall.tangent_coupling),
|
||||
) {
|
||||
velocity = response;
|
||||
position = old_position.add(velocity);
|
||||
hit_wall = Some(wall.id);
|
||||
self.last_collision_id = Some(wall.id);
|
||||
break;
|
||||
) && best_collision
|
||||
.is_none_or(|(_, _, closest)| response.progress < closest.progress)
|
||||
{
|
||||
best_collision = Some((wall.id, true, response));
|
||||
}
|
||||
}
|
||||
let circle = PASSIVE_CIRCLES
|
||||
.iter()
|
||||
.chain(BUMPERS.iter())
|
||||
.find(|circle| circle.id == object_id);
|
||||
if let Some(circle) = circle {
|
||||
let mut response = velocity;
|
||||
if collide_with_circle(
|
||||
if let Some(circle) = circle
|
||||
&& let Some(response) = circle_collision_response(
|
||||
old_position,
|
||||
&mut response,
|
||||
velocity,
|
||||
self.live_circle_center(circle.id, circle.center),
|
||||
circle.contact_radius,
|
||||
f64::from(circle.normal_rebound),
|
||||
f64::from(circle.tangent_coupling),
|
||||
f64::from(circle.normal_kick),
|
||||
) {
|
||||
velocity = response;
|
||||
position = old_position.add(velocity);
|
||||
hit_circle = Some(circle.id);
|
||||
self.last_collision_id = Some(circle.id);
|
||||
break;
|
||||
}
|
||||
)
|
||||
&& best_collision.is_none_or(|(_, _, closest)| response.progress < closest.progress)
|
||||
{
|
||||
best_collision = Some((circle.id, false, response));
|
||||
}
|
||||
}
|
||||
let (hit_wall, hit_circle) = if let Some((object_id, is_wall, response)) = best_collision {
|
||||
velocity = response.velocity;
|
||||
position = old_position.add(velocity);
|
||||
self.last_collision_id = Some(object_id);
|
||||
if is_wall {
|
||||
(Some(object_id), None)
|
||||
} else {
|
||||
(None, Some(object_id))
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
self.ball.position = position.to_position();
|
||||
self.ball.velocity = velocity.to_velocity_per_second();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user