Skip to main content

box3d_rust/math_functions/
quat.rs

1// Quaternion operations.
2// Part of the math_functions module.
3
4use super::*;
5
6/// Does the supplied quaternion have unit length?
7pub fn is_normalized_quat(q: Quat) -> bool {
8    let qq = q.v.x * q.v.x + q.v.y * q.v.y + q.v.z * q.v.z + q.s * q.s;
9    1.0 - 20.0 * f32::EPSILON < qq && qq < 1.0 + 20.0 * f32::EPSILON
10}
11
12/// Rotate a vector.
13pub fn rotate_vector(q: Quat, v: Vec3) -> Vec3 {
14    // v + 2 * cross(q.v, cross(q.v, v) + q.s * v)
15    // B3_ASSERT( b3IsNormalizedQuat( q ) );
16    let t1 = cross(q.v, v);
17    let t2 = mul_add(t1, q.s, v);
18    let t3 = cross(q.v, t2);
19    mul_add(v, 2.0, t3)
20}
21
22/// Inverse rotate a vector.
23pub fn inv_rotate_vector(q: Quat, v: Vec3) -> Vec3 {
24    // v + 2 * cross(q.v, cross(q.v, v) - q.s * v)
25    // B3_ASSERT( b3IsNormalizedQuat( q ) );
26    let t1 = cross(q.v, v);
27    let t2 = mul_sub(t1, q.s, v);
28    let t3 = cross(q.v, t2);
29    mul_add(v, 2.0, t3)
30}
31
32/// Compute dot product of two quaternions. Useful for polarity tests.
33pub fn dot_quat(a: Quat, b: Quat) -> f32 {
34    a.v.x * b.v.x + a.v.y * b.v.y + a.v.z * b.v.z + a.s * b.s
35}
36
37/// Multiply two quaternions.
38pub fn mul_quat(q1: Quat, q2: Quat) -> Quat {
39    let t1 = cross(q1.v, q2.v);
40    let t2 = mul_add(t1, q1.s, q2.v);
41    let t3 = mul_add(t2, q2.s, q1.v);
42    Quat {
43        v: t3,
44        s: q1.s * q2.s - dot(q1.v, q2.v),
45    }
46}
47
48/// Compute a relative quaternion.
49/// inv(q1) * q2
50pub fn inv_mul_quat(q1: Quat, q2: Quat) -> Quat {
51    let t1 = cross(q2.v, q1.v);
52    let t2 = mul_add(t1, q1.s, q2.v);
53    let t3 = mul_sub(t2, q2.s, q1.v);
54    Quat {
55        v: t3,
56        s: q1.s * q2.s + dot(q1.v, q2.v),
57    }
58}
59
60/// Quaternion conjugate (cheap inverse).
61pub fn conjugate(q: Quat) -> Quat {
62    Quat {
63        v: Vec3 {
64            x: -q.v.x,
65            y: -q.v.y,
66            z: -q.v.z,
67        },
68        s: q.s,
69    }
70}
71
72/// Component-wise quaternion negation.
73pub fn negate_quat(q: Quat) -> Quat {
74    Quat {
75        v: Vec3 {
76            x: -q.v.x,
77            y: -q.v.y,
78            z: -q.v.z,
79        },
80        s: -q.s,
81    }
82}
83
84/// Normalize a quaternion.
85pub fn normalize_quat(q: Quat) -> Quat {
86    let length_sq = dot_quat(q, q);
87    if length_sq > 1000.0 * f32::MIN_POSITIVE {
88        let s = 1.0 / length_sq.sqrt();
89        Quat {
90            v: Vec3 {
91                x: s * q.v.x,
92                y: s * q.v.y,
93                z: s * q.v.z,
94            },
95            s: s * q.s,
96        }
97    } else {
98        QUAT_IDENTITY
99    }
100}
101
102/// Integrate rotation from angular velocity.
103/// `delta_rotation` is the angular displacement in radians (ω · h).
104/// q2 = q1 + 0.5 * omega * q1
105/// (math_internal.h: b3IntegrateRotation)
106pub fn integrate_rotation(q1: Quat, delta_rotation: Vec3) -> Quat {
107    // https://fgiesen.wordpress.com/2012/08/24/quaternion-differentiation/
108    let mut qd = Quat {
109        v: mul_sv(0.5, delta_rotation),
110        s: 0.0,
111    };
112    qd = mul_quat(qd, q1);
113    let q2 = Quat {
114        v: add(q1.v, qd.v),
115        s: qd.s + q1.s,
116    };
117    normalize_quat(q2)
118}
119
120/// Make a quaternion that is equivalent to rotating around an axis by a specified angle.
121pub fn make_quat_from_axis_angle(axis: Vec3, radians: f32) -> Quat {
122    debug_assert!(is_normalized(axis));
123    let cs = compute_cos_sin(0.5 * radians);
124    Quat {
125        v: Vec3 {
126            x: cs.sine * axis.x,
127            y: cs.sine * axis.y,
128            z: cs.sine * axis.z,
129        },
130        s: cs.cosine,
131    }
132}
133
134/// Get the axis and angle from a quaternion. Assumes the quaternion is normalized.
135pub fn get_axis_angle(radians: &mut f32, q: Quat) -> Vec3 {
136    let length = (q.v.x * q.v.x + q.v.y * q.v.y + q.v.z * q.v.z).sqrt();
137    *radians = 2.0 * atan2(length, q.s);
138    if length > 0.0 {
139        let inv_length = 1.0 / length;
140        Vec3 {
141            x: inv_length * q.v.x,
142            y: inv_length * q.v.y,
143            z: inv_length * q.v.z,
144        }
145    } else {
146        VEC3_ZERO
147    }
148}
149
150/// Get the angle for a quaternion in radians
151pub fn get_quat_angle(q: Quat) -> f32 {
152    let length = (q.v.x * q.v.x + q.v.y * q.v.y + q.v.z * q.v.z).sqrt();
153    2.0 * atan2(length, q.s)
154}
155
156/// Extract a quaternion from a rotation matrix.
157pub fn make_quat_from_matrix(m: &Matrix3) -> Quat {
158    let c1 = m.cx;
159    let c2 = m.cy;
160    let c3 = m.cz;
161
162    let q: Quat;
163
164    let trace = m.cx.x + m.cy.y + m.cz.z;
165    if trace >= 0.0 {
166        q = Quat {
167            v: Vec3 {
168                x: c2.z - c3.y,
169                y: c3.x - c1.z,
170                z: c1.y - c2.x,
171            },
172            s: trace + 1.0,
173        };
174    } else if c1.x > c2.y && c1.x > c3.z {
175        q = Quat {
176            v: Vec3 {
177                x: c1.x - c2.y - c3.z + 1.0,
178                y: c2.x + c1.y,
179                z: c3.x + c1.z,
180            },
181            s: c2.z - c3.y,
182        };
183    } else if c2.y > c3.z {
184        q = Quat {
185            v: Vec3 {
186                x: c1.y + c2.x,
187                y: c2.y - c3.z - c1.x + 1.0,
188                z: c3.y + c2.z,
189            },
190            s: c3.x - c1.z,
191        };
192    } else {
193        q = Quat {
194            v: Vec3 {
195                x: c1.z + c3.x,
196                y: c2.z + c3.y,
197                z: c3.z - c1.x - c2.y + 1.0,
198            },
199            s: c1.y - c2.x,
200        };
201    }
202
203    // The algorithm is simplified and made more accurate by normalizing at the end
204    normalize_quat(q)
205}
206
207/// Find a quaternion that rotates one vector to another.
208pub fn compute_quat_between_unit_vectors(v1: Vec3, v2: Vec3) -> Quat {
209    debug_assert!(is_normalized(v1));
210    debug_assert!(is_normalized(v2));
211
212    let out: Quat;
213
214    let m = lerp(v1, v2, 0.5);
215    let tolerance = 100.0 * f32::EPSILON;
216    if length_squared(m) > tolerance * tolerance {
217        out = Quat {
218            v: cross(v1, m),
219            s: dot(v1, m),
220        };
221    } else {
222        // Anti-parallel: Use a perpendicular vector
223        if abs_float(v1.x) > 0.5 {
224            out = Quat {
225                v: Vec3 {
226                    x: v1.y,
227                    y: -v1.x,
228                    z: 0.0,
229                },
230                s: 0.0,
231            };
232        } else {
233            out = Quat {
234                v: Vec3 {
235                    x: 0.0,
236                    y: v1.z,
237                    z: -v1.y,
238                },
239                s: 0.0,
240            };
241        }
242    }
243
244    // The algorithm is simplified and made more accurate by normalizing at the end
245    normalize_quat(out)
246}
247
248/// Twist angle around the z-axis, used for twist limit and revolute angle limit
249pub fn get_twist_angle(q: Quat) -> f32 {
250    // Account for polarity to keep the twist angle in range.
251    // This is simpler than asking the user to check polarity or unwinding.
252    let mut twist = if q.s < 0.0 {
253        atan2(-q.v.z, -q.s)
254    } else {
255        atan2(q.v.z, q.s)
256    };
257    twist *= 2.0;
258    debug_assert!((-PI..=PI).contains(&twist));
259    twist
260}
261
262/// Pseudo angular velocity from a quaternion target.
263/// `w = 2 * (target - q) * conj(q)` (math_internal.h: b3DeltaQuatToRotation)
264pub fn delta_quat_to_rotation(q: Quat, target: Quat) -> Vec3 {
265    let mut s = q;
266    if dot_quat(q, target) < 0.0 {
267        // Correct polarity
268        s = negate_quat(q);
269    }
270
271    let diff = Quat {
272        v: sub(target.v, s.v),
273        s: target.s - s.s,
274    };
275    let product = mul_quat(diff, conjugate(s));
276    mul_sv(2.0, product.v)
277}
278
279/// Swing angle used for cone limit
280pub fn get_swing_angle(q: Quat) -> f32 {
281    // Polarity should not matter because all terms are squared.
282    let x = (q.v.z * q.v.z + q.s * q.s).sqrt();
283    let y = (q.v.x * q.v.x + q.v.y * q.v.y).sqrt();
284    let swing = 2.0 * atan2(y, x);
285    debug_assert!((0.0..=PI).contains(&swing));
286    swing
287}
288
289/// Linearly interpolate and normalize between two quaternions
290pub fn nlerp(mut q1: Quat, q2: Quat, alpha: f32) -> Quat {
291    debug_assert!((0.0..=1.0).contains(&alpha));
292    if dot_quat(q1, q2) < 0.0 {
293        q1 = Quat {
294            v: Vec3 {
295                x: -q1.v.x,
296                y: -q1.v.y,
297                z: -q1.v.z,
298            },
299            s: -q1.s,
300        };
301    }
302
303    let q = Quat {
304        v: lerp(q1.v, q2.v, alpha),
305        s: (1.0 - alpha) * q1.s + alpha * q2.s,
306    };
307
308    normalize_quat(q)
309}