diff --git a/src/physics.rs b/src/physics.rs index 147a183..da97c33 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -121,11 +121,12 @@ pub fn get_candidate (world: &[Triangle], p0: Vec3, p1: Vec3, radius: f32) let passed_plane = distance_to_face0 > 0.0 && distance_to_face1 < 0.0; if passed_plane { - let t = distance_to_face0 / (distance_to_face0 - distance_to_face1); + let denom = distance_to_face0 - distance_to_face1; + let t_times_denom = distance_to_face0; // Because of previous early returns we know that 0.0 < t < 1.0 - let p_impact = p0 * (1.0 - t) + p1 * (t); + let p_impact_times_denom = p0 * (denom - t_times_denom) + p1 * (t_times_denom); let impact_inside_tri = (|| { for j in 0..3 { @@ -133,7 +134,7 @@ pub fn get_candidate (world: &[Triangle], p0: Vec3, p1: Vec3, radius: f32) let b = tri.verts [(j + 1) % 3]; let tangent = Vec3::cross (b - a, normal); - if Vec3::dot (tangent, p_impact - a) < 0.0 { + if Vec3::dot (tangent, p_impact_times_denom - a * denom) < 0.0 { return false; } } @@ -144,8 +145,8 @@ pub fn get_candidate (world: &[Triangle], p0: Vec3, p1: Vec3, radius: f32) // Stop it let c = Collision { - t, - p_impact, + t: t_times_denom / denom, + p_impact: p_impact_times_denom / denom, normal, i, }; @@ -343,12 +344,6 @@ mod test { fn test_physics () { // Remember, Z is up - let params = Params { - dt: 1.0, - gravity: (0.0, 0.0, 0.0).into (), - margin: 0.00125, - }; - let world: Vec <_> = vec! [ ( (0.0, 0.0, 0.0), @@ -367,6 +362,12 @@ mod test { }) .collect (); + let params = Params { + dt: 1.0, + gravity: (0.0, 0.0, 0.0).into (), + margin: 0.00125, + }; + let magic_0 = f32::sqrt (f32::powf (0.5 + params.margin, 2.0) / 2.0); for ((radius, body_before), e) in [ @@ -450,5 +451,66 @@ mod test { assert_eq! (a.triangles_hit, e.triangles_hit); assert_eq! (a.kill, e.kill); } + + // With no bounce, a ball should settle on a flat triangle in one + // frame and reach a steady state + + let params = Params { + dt: 1.0, + gravity: (0.0, 0.0, -1.0).into (), + margin: 0.00125, + }; + + let radius = 0.5; + let body_before = PhysicsBody { + pos: (0.5, 0.5, 2.0).into (), + vel: (0.0, 0.0, -4.0).into (), + }; + + assert_eq! ( + get_candidate (&world, (0.5, 0.5, 2.0).into (), (0.5, 0.5, -3.0).into (), radius), + Collision { + t: 0.3, + p_impact: (0.5, 0.5, 0.5).into (), + normal: (0.0, 0.0, 1.0).into (), + i: 0, + }, + ); + + let a = physics_step (¶ms, &world, radius, &body_before); + + let e = PhysicsResult { + body: PhysicsBody { + pos: (0.5, 0.5, 0.5 + params.margin).into (), + vel: (0.0, 0.0, 0.0).into (), + }, + triangles_hit: vec! [0], + kill: false, + }; + + // Fixed point should be here + // If this test passes, at least a ball can rest on a single horizontal + // triangle. If it fails, that means the ball is not resting + // (maybe the velocity is non-zero even if the position is stable) + // or the ball is going to gain energy and bounce away, or it's going + // to slide through the triangle. + + let body_before = PhysicsBody { + pos: (0.5, 0.5, 0.5 + params.margin).into (), + vel: (0.0, 0.0, 0.0).into (), + }; + + let a = physics_step (¶ms, &world, radius, &body_before); + + let e = PhysicsResult { + body: PhysicsBody { + pos: (0.5, 0.5, 0.5 + params.margin).into (), + vel: (0.0, 0.0, 0.0).into (), + }, + triangles_hit: vec! [0], + kill: false, + }; + + assert_eq! (a, e); } }