Skip to main content

box2d_rust/math_functions/
transform.rs

1// Rigid transforms, world-position (large-world) operations, and 2x2 matrices.
2// Part of the math_functions module.
3
4use super::*;
5
6/// Transform a point (e.g. local space to world space)
7pub fn transform_point(t: Transform, p: Vec2) -> Vec2 {
8    let x = (t.q.c * p.x - t.q.s * p.y) + t.p.x;
9    let y = (t.q.s * p.x + t.q.c * p.y) + t.p.y;
10
11    Vec2 { x, y }
12}
13
14/// Inverse transform a point (e.g. world space to local space)
15pub fn inv_transform_point(t: Transform, p: Vec2) -> Vec2 {
16    let vx = p.x - t.p.x;
17    let vy = p.y - t.p.y;
18    Vec2 {
19        x: t.q.c * vx + t.q.s * vy,
20        y: -t.q.s * vx + t.q.c * vy,
21    }
22}
23
24/// Multiply two transforms. If the result is applied to a point p local to frame B,
25/// the transform would first convert p to a point local to frame A, then into a point
26/// in the world frame.
27/// v2 = A.q.Rot(B.q.Rot(v1) + B.p) + A.p
28///    = (A.q * B.q).Rot(v1) + A.q.Rot(B.p) + A.p
29pub fn mul_transforms(a: Transform, b: Transform) -> Transform {
30    Transform {
31        q: mul_rot(a.q, b.q),
32        p: add(rotate_vector(a.q, b.p), a.p),
33    }
34}
35
36/// Creates a transform that converts a local point in frame B to a local point in frame A.
37/// v2 = A.q' * (B.q * v1 + B.p - A.p)
38///    = A.q' * B.q * v1 + A.q' * (B.p - A.p)
39pub fn inv_mul_transforms(a: Transform, b: Transform) -> Transform {
40    Transform {
41        q: inv_mul_rot(a.q, b.q),
42        p: inv_rotate_vector(a.q, sub(b.p, a.p)),
43    }
44}
45
46/// Convert a vector to a world position. no-op in single precision.
47pub fn to_pos(v: Vec2) -> Pos {
48    Pos {
49        x: v.x as _,
50        y: v.y as _,
51    }
52}
53
54/// Lossy conversion of a world position to a float vector. no-op in single precision.
55pub fn to_vec2(p: Pos) -> Vec2 {
56    Vec2 {
57        x: p.x as f32,
58        y: p.y as f32,
59    }
60}
61
62/// Narrow a world coordinate to float, rounding toward negative infinity. Use with
63/// [`round_up_float`] to build a conservative float box that always contains double bounds,
64/// where plain rounding far from the origin could clip. nextafterf is an exact IEEE
65/// operation, so this is cross-platform deterministic. With large world mode off this is
66/// a plain conversion.
67#[cfg(feature = "double-precision")]
68pub fn round_down_float(x: f64) -> f32 {
69    let f = x as f32;
70    if f as f64 > x {
71        next_after_f32(f, f32::MIN)
72    } else {
73        f
74    }
75}
76
77/// Narrow a world coordinate to float, rounding toward positive infinity.
78#[cfg(feature = "double-precision")]
79pub fn round_up_float(x: f64) -> f32 {
80    let f = x as f32;
81    if (f as f64) < x {
82        next_after_f32(f, f32::MAX)
83    } else {
84        f
85    }
86}
87
88#[cfg(not(feature = "double-precision"))]
89pub fn round_down_float(x: f64) -> f32 {
90    x as f32
91}
92
93#[cfg(not(feature = "double-precision"))]
94pub fn round_up_float(x: f64) -> f32 {
95    x as f32
96}
97
98/// C nextafterf: the next representable f32 after `from` in the direction of `to`.
99#[cfg(feature = "double-precision")]
100fn next_after_f32(from: f32, to: f32) -> f32 {
101    if from.is_nan() || to.is_nan() {
102        return f32::NAN;
103    }
104    if from == to {
105        return to;
106    }
107    if from == 0.0 {
108        return if to > 0.0 {
109            f32::from_bits(1)
110        } else {
111            -f32::from_bits(1)
112        };
113    }
114    let bits = from.to_bits();
115    let next = if (from < to) == (from > 0.0) {
116        bits + 1
117    } else {
118        bits - 1
119    };
120    f32::from_bits(next)
121}
122
123/// a - b, demoted to float. The primary precision boundary operation.
124pub fn sub_pos(a: Pos, b: Pos) -> Vec2 {
125    Vec2 {
126        x: (a.x - b.x) as f32,
127        y: (a.y - b.y) as f32,
128    }
129}
130
131/// p + d
132pub fn offset_pos(p: Pos, d: Vec2) -> Pos {
133    Pos {
134        x: p.x + d.x as PosScalar,
135        y: p.y + d.y as PosScalar,
136    }
137}
138
139/// World position interpolation for sweeps and sampling.
140pub fn lerp_position(a: Pos, b: Pos, t: f32) -> Pos {
141    Pos {
142        x: (1.0 - t) as PosScalar * a.x + t as PosScalar * b.x,
143        y: (1.0 - t) as PosScalar * a.y + t as PosScalar * b.y,
144    }
145}
146
147#[cfg(feature = "double-precision")]
148type PosScalar = f64;
149#[cfg(not(feature = "double-precision"))]
150type PosScalar = f32;
151
152/// Transform a local point to a world position. Rotation in float, translation in double.
153pub fn transform_world_point(t: WorldTransform, p: Vec2) -> Pos {
154    let rx = t.q.c * p.x - t.q.s * p.y;
155    let ry = t.q.s * p.x + t.q.c * p.y;
156    Pos {
157        x: t.p.x + rx as PosScalar,
158        y: t.p.y + ry as PosScalar,
159    }
160}
161
162/// Transform a world position to a local point. One double subtraction, then float.
163pub fn inv_transform_world_point(t: WorldTransform, p: Pos) -> Vec2 {
164    let vx = (p.x - t.p.x) as f32;
165    let vy = (p.y - t.p.y) as f32;
166    Vec2 {
167        x: t.q.c * vx + t.q.s * vy,
168        y: -t.q.s * vx + t.q.c * vy,
169    }
170}
171
172/// Relative transform of frame B in frame A.
173pub fn inv_mul_world_transforms(a: WorldTransform, b: WorldTransform) -> Transform {
174    let d = Vec2 {
175        x: (b.p.x - a.p.x) as f32,
176        y: (b.p.y - a.p.y) as f32,
177    };
178    Transform {
179        q: inv_mul_rot(a.q, b.q),
180        p: inv_rotate_vector(a.q, d),
181    }
182}
183
184/// Convert a local transform B into world space using world transform A.
185pub fn offset_world_transform(a: WorldTransform, b: Transform) -> WorldTransform {
186    WorldTransform {
187        q: mul_rot(a.q, b.q),
188        p: offset_pos(a.p, rotate_vector(a.q, b.p)),
189    }
190}
191
192/// Shift a world transform into the frame of a base position.
193pub fn to_relative_transform(t: WorldTransform, base: Pos) -> Transform {
194    Transform {
195        q: t.q,
196        p: Vec2 {
197            x: (t.p.x - base.x) as f32,
198            y: (t.p.y - base.y) as f32,
199        },
200    }
201}
202
203/// Promote a float transform to a world transform. Lossless.
204pub fn make_world_transform(t: Transform) -> WorldTransform {
205    WorldTransform {
206        p: to_pos(t.p),
207        q: t.q,
208    }
209}
210
211/// Multiply a 2-by-2 matrix times a 2D vector
212pub fn mul_mv(a: Mat22, v: Vec2) -> Vec2 {
213    Vec2 {
214        x: a.cx.x * v.x + a.cy.x * v.y,
215        y: a.cx.y * v.x + a.cy.y * v.y,
216    }
217}
218
219/// Get the inverse of a 2-by-2 matrix
220pub fn get_inverse_22(a: Mat22) -> Mat22 {
221    let (m11, m12, m21, m22) = (a.cx.x, a.cy.x, a.cx.y, a.cy.y);
222    let mut det = m11 * m22 - m12 * m21;
223    if det != 0.0 {
224        det = 1.0 / det;
225    }
226
227    Mat22 {
228        cx: Vec2 {
229            x: det * m22,
230            y: -det * m21,
231        },
232        cy: Vec2 {
233            x: -det * m12,
234            y: det * m11,
235        },
236    }
237}
238
239/// Solve A * x = b, where b is a column vector. This is more efficient
240/// than computing the inverse in one-shot cases.
241pub fn solve_22(a: Mat22, b: Vec2) -> Vec2 {
242    let (a11, a12, a21, a22) = (a.cx.x, a.cy.x, a.cx.y, a.cy.y);
243    let mut det = a11 * a22 - a12 * a21;
244    if det != 0.0 {
245        det = 1.0 / det;
246    }
247    Vec2 {
248        x: det * (a22 * b.x - a12 * b.y),
249        y: det * (a11 * b.y - a21 * b.x),
250    }
251}