1use crate::constants::linear_slop;
10use crate::math_functions::{
11 abs_float, clamp_float, dot, min_float, mul_add, mul_sub, plane_separation, Plane, Vec2,
12};
13
14#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct CollisionPlane {
19 pub plane: Plane,
21
22 pub push_limit: f32,
25
26 pub push: f32,
29
30 pub clip_velocity: bool,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Default)]
37pub struct PlaneSolverResult {
38 pub translation: Vec2,
40
41 pub iteration_count: i32,
43}
44
45pub fn solve_planes(target_delta: Vec2, planes: &mut [CollisionPlane]) -> PlaneSolverResult {
50 for plane in planes.iter_mut() {
51 plane.push = 0.0;
52 }
53
54 let mut delta = target_delta;
55 let tolerance = linear_slop();
56
57 let mut iteration = 0;
58 while iteration < 20 {
59 let mut total_push = 0.0;
60 for plane in planes.iter_mut() {
61 let separation = plane_separation(plane.plane, delta) + linear_slop();
63
64 let push = -separation;
65
66 let accumulated_push = plane.push;
68 plane.push = clamp_float(plane.push + push, 0.0, plane.push_limit);
69 let push = plane.push - accumulated_push;
70 delta = mul_add(delta, push, plane.plane.normal);
71
72 total_push += abs_float(push);
74 }
75
76 if total_push < tolerance {
77 break;
78 }
79
80 iteration += 1;
81 }
82
83 PlaneSolverResult {
84 translation: delta,
85 iteration_count: iteration,
86 }
87}
88
89pub fn clip_vector(vector: Vec2, planes: &[CollisionPlane]) -> Vec2 {
92 let mut v = vector;
93
94 for plane in planes.iter() {
95 if plane.push == 0.0 || !plane.clip_velocity {
96 continue;
97 }
98
99 v = mul_sub(
100 v,
101 min_float(0.0, dot(v, plane.plane.normal)),
102 plane.plane.normal,
103 );
104 }
105
106 v
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::math_functions::normalize;
113
114 #[test]
117 fn solve_and_clip_against_floor() {
118 let floor = Plane {
120 normal: Vec2 { x: 0.0, y: 1.0 },
121 offset: -0.05,
122 };
123 let mut planes = [CollisionPlane {
124 plane: floor,
125 push_limit: f32::MAX,
126 push: 0.0,
127 clip_velocity: true,
128 }];
129
130 let target = Vec2 { x: 1.0, y: -1.0 };
132 let result = solve_planes(target, &mut planes);
133
134 assert!((result.translation.x - 1.0).abs() < 1e-6);
137 assert!(result.translation.y > -0.1);
138 assert!(planes[0].push > 0.0);
139
140 let velocity = Vec2 { x: 2.0, y: -3.0 };
142 let clipped = clip_vector(velocity, &planes);
143 assert!((clipped.x - 2.0).abs() < 1e-6);
144 assert!(clipped.y.abs() < 1e-6);
145
146 planes[0].clip_velocity = false;
148 let unclipped = clip_vector(velocity, &planes);
149 assert_eq!(unclipped, velocity);
150 }
151
152 #[test]
154 fn solve_planes_wedge() {
155 let mut planes = [
156 CollisionPlane {
157 plane: Plane {
158 normal: normalize(Vec2 { x: 1.0, y: 1.0 }),
159 offset: 0.0,
160 },
161 push_limit: f32::MAX,
162 push: 0.0,
163 clip_velocity: true,
164 },
165 CollisionPlane {
166 plane: Plane {
167 normal: normalize(Vec2 { x: -1.0, y: 1.0 }),
168 offset: 0.0,
169 },
170 push_limit: f32::MAX,
171 push: 0.0,
172 clip_velocity: true,
173 },
174 ];
175
176 let result = solve_planes(Vec2 { x: 0.0, y: -1.0 }, &mut planes);
179 for plane in planes.iter() {
180 assert!(plane_separation(plane.plane, result.translation) > -0.02);
181 }
182 assert!(result.iteration_count < 20);
183 }
184}