GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
142 of 160, 0 excluded
88.8%
Functions:
33 of 33, 0 excluded
100.0%
Branches:
67 of 144, 0 excluded
46.5%

libs/base/src/eu/base/quat.cc
Line Branch Exec Source
1 #include "eu/base/quat.h"
2
3 #include <cmath>
4
5
6 namespace eu
7 {
8 1115 v3 Q::get_vec_part() const
9 {
10 1115 return {x, y, z};
11 }
12
13
14 [[nodiscard]] Q
15 1588 Q::from(const AA& aa)
16 {
17
2/4
✓ Branch 2 → 3 taken 1588 times.
✗ Branch 2 → 11 not taken.
✓ Branch 3 → 4 taken 1588 times.
✗ Branch 3 → 11 not taken.
1588 const float sin_a = sin(aa.angle / 2);
18
2/4
✓ Branch 4 → 5 taken 1588 times.
✗ Branch 4 → 12 not taken.
✓ Branch 5 → 6 taken 1588 times.
✗ Branch 5 → 12 not taken.
1588 const float cos_a = cos(aa.angle / 2);
19
1/2
✓ Branch 6 → 7 taken 1588 times.
✗ Branch 6 → 13 not taken.
1588 Q r(cos_a, aa.axis * sin_a);
20 1588 r.normalize();
21 1588 return r;
22 }
23
24
25 [[nodiscard]] Q
26 513 Q::from(const Ypr& ypr)
27 {
28
3/6
✓ Branch 2 → 3 taken 513 times.
✗ Branch 2 → 16 not taken.
✓ Branch 3 → 4 taken 513 times.
✗ Branch 3 → 16 not taken.
✓ Branch 4 → 5 taken 513 times.
✗ Branch 4 → 16 not taken.
513 const auto yaw = Q::from(AA{kk::y_axis, -ypr.yaw});
29
3/6
✓ Branch 5 → 6 taken 513 times.
✗ Branch 5 → 18 not taken.
✓ Branch 6 → 7 taken 513 times.
✗ Branch 6 → 18 not taken.
✓ Branch 7 → 8 taken 513 times.
✗ Branch 7 → 18 not taken.
513 const auto pitch = Q::from(AA{ kk::x_axis, -ypr.pitch});
30
1/2
✓ Branch 8 → 9 taken 513 times.
✗ Branch 8 → 22 not taken.
513 const auto yp = pitch.then_get_rotated(yaw);
31
3/6
✓ Branch 9 → 10 taken 513 times.
✗ Branch 9 → 20 not taken.
✓ Branch 10 → 11 taken 513 times.
✗ Branch 10 → 20 not taken.
✓ Branch 11 → 12 taken 513 times.
✗ Branch 11 → 20 not taken.
513 const auto roll = Q::from(AA{ yp.get_local_out(), ypr.roll });
32
1/2
✓ Branch 12 → 13 taken 513 times.
✗ Branch 12 → 22 not taken.
1026 return yp.then_get_rotated(roll);
33 }
34
35 [[nodiscard]] Q
36 512 Q::from_fast(const Ypr& ypr)
37 {
38 // Abbreviations for the various angular functions
39
2/4
✓ Branch 2 → 3 taken 512 times.
✗ Branch 2 → 19 not taken.
✓ Branch 3 → 4 taken 512 times.
✗ Branch 3 → 19 not taken.
512 const auto cy = cos(ypr.yaw * 0.5);
40
2/4
✓ Branch 4 → 5 taken 512 times.
✗ Branch 4 → 20 not taken.
✓ Branch 5 → 6 taken 512 times.
✗ Branch 5 → 20 not taken.
512 const auto sy = sin(ypr.yaw * 0.5);
41
2/4
✓ Branch 6 → 7 taken 512 times.
✗ Branch 6 → 21 not taken.
✓ Branch 7 → 8 taken 512 times.
✗ Branch 7 → 21 not taken.
512 const auto cp = cos(ypr.pitch * 0.5);
42
2/4
✓ Branch 8 → 9 taken 512 times.
✗ Branch 8 → 22 not taken.
✓ Branch 9 → 10 taken 512 times.
✗ Branch 9 → 22 not taken.
512 const auto sp = sin(ypr.pitch * 0.5);
43
2/4
✓ Branch 10 → 11 taken 512 times.
✗ Branch 10 → 23 not taken.
✓ Branch 11 → 12 taken 512 times.
✗ Branch 11 → 23 not taken.
512 const auto cr = cos(ypr.roll * 0.5);
44
2/4
✓ Branch 12 → 13 taken 512 times.
✗ Branch 12 → 24 not taken.
✓ Branch 13 → 14 taken 512 times.
✗ Branch 13 → 24 not taken.
512 const auto sr = sin(ypr.roll * 0.5);
45
46 return
47 {
48 512 cy * cp * cr + sy * sp * sr,
49 {
50 512 -(cy * sp * cr + sy * cp * sr),
51 512 cy * sp * sr - sy * cp * cr,
52 512 cy * cp * sr - sy * sp * cr
53 }
54 512 };
55 }
56
57
58 [[nodiscard]] Q
59 1 Q::from_to(const Q& from, const Q& to)
60 {
61 // https://stackoverflow.com/a/22167097
62
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 6 not taken.
1 return to * from.get_inverse();
63 }
64
65
66 [[nodiscard]] std::optional<Q>
67 3 Q::look_at(const v3& from, const v3& to, const n3& up)
68 {
69
2/4
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 13 not taken.
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 13 not taken.
3 const auto direction = v3::from_to(from, to).get_normalized();
70
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 3 times.
3 if (direction.has_value() == false)
71 { return std::nullopt; }
72
1/2
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 14 not taken.
3 return look_in_direction(*direction, up);
73 }
74
75
76 Q
77 1030 Q::then_get_rotated(const Q& q) const
78 {
79 1030 return q * *this;
80 }
81
82
83 Q
84 555 Q::get_conjugate() const
85 {
86
1/2
✓ Branch 3 → 4 taken 555 times.
✗ Branch 3 → 8 not taken.
555 return {w, -get_vec_part()};
87 }
88
89
90 Q
91 2 Q::get_inverse() const
92 {
93
1/6
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 11 taken 2 times.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 11 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 13 not taken.
2 ASSERT(is_equal(get_length(), 1.0f));
94 2 return get_conjugate();
95 }
96
97
98 // the negated represents the same rotation
99 Q
100 1 Q::get_negated() const
101 {
102
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 8 not taken.
1 return {-w, -get_vec_part()};
103 }
104
105 float
106 1595 Q::get_length() const
107 {
108 1595 const auto l2 = x * x + y * y + z * z + w * w;
109 1595 return std::sqrt(l2);
110 }
111
112
113 void
114 1593 Q::normalize()
115 {
116 1593 const float l = get_length();
117
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1593 times.
1593 if(is_zero(l))
118 {
119 *this = q_identity;
120 }
121 else
122 {
123 1593 x /= l;
124 1593 y /= l;
125 1593 z /= l;
126 1593 w /= l;
127 }
128 1593 }
129
130
131 Q
132 5 Q::get_normalized() const
133 {
134 5 Q r = *this;
135 5 r.normalize();
136 5 return r;
137 }
138
139
140
2/4
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 7 not taken.
✓ Branch 3 → 4 taken 9 times.
✗ Branch 3 → 7 not taken.
9 n3 Q::get_local_in () const { return get_rotated(-kk::z_axis); }
141 517 n3 Q::get_local_out () const { return get_rotated( kk::z_axis); }
142 9 n3 Q::get_local_right() const { return get_rotated( kk::x_axis); }
143
2/4
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 7 not taken.
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 7 not taken.
4 n3 Q::get_local_left () const { return get_rotated(-kk::x_axis); }
144 9 n3 Q::get_local_up () const { return get_rotated( kk::y_axis); }
145
2/4
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 7 not taken.
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 7 not taken.
4 n3 Q::get_local_down () const { return get_rotated(-kk::y_axis); }
146
147
148 n3
149 552 Q::get_rotated(const n3& v) const
150 {
151 // http://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion
152 552 const Q pure = {0, v};
153 552 const Q a = *this * pure;
154
1/2
✓ Branch 4 → 5 taken 552 times.
✗ Branch 4 → 18 not taken.
552 const Q ret = a * get_conjugate();
155 // todo(Gustav): should we normalize here? Can we get a invalid vector?
156
1/2
✓ Branch 7 → 8 taken 552 times.
✗ Branch 7 → 19 not taken.
552 const auto normalized = ret.get_vec_part().get_normalized();
157
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 14 taken 552 times.
552 if(normalized.has_value() == false)
158 {
159 DIE("invalid rotation vector");
160 return kk::up;
161 }
162
163 552 return *normalized;
164 }
165
166 10 Q add(const Q& lhs, const Q& rhs)
167 {
168 10 return { lhs.w + rhs.w, {lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z} };
169 }
170
171
172 Q
173 5 Q::nlerp(const Q& f, const float scale, const Q& t)
174 {
175 5 const auto lhs = f * (1 - scale);
176 5 const auto rhs = t * scale;
177 5 return add(lhs, rhs).get_normalized();
178 }
179
180
181 Q
182 5 Q::slerp_fast(const Q& qa, const float t, const Q& qb)
183 {
184 // from:
185 // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
186 // Calculate angle between them.
187 5 const float cos_half_theta = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z;
188 // if qa=qb or qa=-qb then theta = 0 and we can return qa
189
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if(cabs(cos_half_theta) >= 1.0f)
190 {
191 return qa;
192 }
193 // Calculate temporary values.
194
1/2
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 26 not taken.
5 const auto half_theta = eu::acos(cos_half_theta);
195 5 const auto sin_half_theta = std::sqrt(1.0f - cos_half_theta * cos_half_theta);
196
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 5 times.
5 if(cabs(sin_half_theta) < 0.001f)
197 {
198 // if theta = 180 degrees then result is not fully defined
199 // we could rotate around any axis normal to qa or qb
200 const Q qt = add(qa, qb);
201 return Q
202 {
203 qt.w * 0.5f,
204 v3
205 {
206 qt.x * 0.5f,
207 qt.y * 0.5f,
208 qt.z * 0.5f
209 }
210 };
211 }
212
2/4
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 24 not taken.
✓ Branch 15 → 16 taken 5 times.
✗ Branch 15 → 24 not taken.
5 const float ratio_a = eu::sin((1 - t) * half_theta) / sin_half_theta;
213
2/4
✓ Branch 16 → 17 taken 5 times.
✗ Branch 16 → 25 not taken.
✓ Branch 17 → 18 taken 5 times.
✗ Branch 17 → 25 not taken.
5 const float ratio_b = eu::sin(t * half_theta) / sin_half_theta;
214 5 return add(qa * ratio_a, qb * ratio_b);
215 }
216
217
218 Q
219 5 Q::slerp(const Q& from, const float scale, const Q& to)
220 {
221
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 5 times.
5 if(dot(from, to) < 0)
222 {
223 return slerp_fast(from.get_negated(), scale, to);
224 }
225 else
226 {
227 5 return slerp_fast(from, scale, to);
228 }
229 }
230
231
232 void
233 22 Q::operator*=(float rhs)
234 {
235 22 x *= rhs;
236 22 y *= rhs;
237 22 z *= rhs;
238 22 w *= rhs;
239 22 }
240
241
242 void
243 2137 Q::operator*=(const Q& rhs)
244 {
245 #define VAR(a, b) const float a##1##b##2 = a * rhs.b
246 2137 VAR(w, w);
247 2137 VAR(w, x);
248 2137 VAR(w, y);
249 2137 VAR(w, z);
250
251 2137 VAR(x, w);
252 2137 VAR(x, x);
253 2137 VAR(x, y);
254 2137 VAR(x, z);
255
256 2137 VAR(y, w);
257 2137 VAR(y, x);
258 2137 VAR(y, y);
259 2137 VAR(y, z);
260
261 2137 VAR(z, w);
262 2137 VAR(z, x);
263 2137 VAR(z, y);
264 2137 VAR(z, z);
265 #undef VAR
266
267 2137 w = w1w2 - x1x2 - y1y2 - z1z2;
268 2137 x = w1x2 + x1w2 + y1z2 - z1y2;
269 2137 y = w1y2 + y1w2 + z1x2 - x1z2;
270 2137 z = w1z2 + z1w2 + x1y2 - y1x2;
271 2137 }
272
273 6 Q Q::look_in_direction(const n3& dir, const n3&)
274 {
275 // todo(Gustav): does this function work as expected?
276 6 const v3 in = kk::in;
277
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 30 not taken.
6 const float dot_value = in.dot(dir);
278
279
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 6 times.
6 if (cabs(dot_value - (-1.0f)) < 0.000001f)
280 {
281 // todo(Gustav): replace with a constant in general but this line specifically
282 return q_identity; // {3.1415926535897932f, up};
283 }
284
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 5 times.
6 if (cabs(dot_value - (1.0f)) < 0.000001f)
285 {
286 1 return q_identity;
287 }
288
289
1/2
✓ Branch 9 → 10 taken 5 times.
✗ Branch 9 → 30 not taken.
5 const auto rot_angle = acos(dot_value);
290
2/4
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 25 not taken.
✓ Branch 11 → 12 taken 5 times.
✗ Branch 11 → 25 not taken.
5 const auto rot_axis = in.cross(dir).get_normalized();
291
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 18 taken 5 times.
5 if(rot_axis.has_value() == false)
292 {
293 DIE("missing rot_axis");
294 return q_identity;
295 }
296
2/4
✓ Branch 19 → 20 taken 5 times.
✗ Branch 19 → 29 not taken.
✓ Branch 20 → 21 taken 5 times.
✗ Branch 20 → 29 not taken.
5 return Q::from(rha(*rot_axis, rot_angle));
297 }
298
299
300 2 std::string string_from(const Q& v)
301 {
302
1/2
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 5 not taken.
2 return fmt::format("({}, ({}, {}, {}))", v.w, v.x, v.y, v.z);
303 }
304
305
306 float
307 5 dot(const Q& lhs, const Q& rhs)
308 {
309 5 return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w;
310 }
311
312
313 2137 Q operator*(const Q& lhs, const Q& rhs)
314 {
315 2137 Q r = lhs;
316 2137 r *= rhs;
317 2137 return r;
318 }
319
320
321 1 Q operator*(float scale, const Q& q)
322 {
323 1 Q r = q;
324 1 r *= scale;
325 1 return r;
326 }
327
328
329 21 Q operator*(const Q& q, float scale)
330 {
331 21 Q r = q;
332 21 r *= scale;
333 21 return r;
334 }
335
336
2/4
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 10 not taken.
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 8 not taken.
1 ADD_CATCH_FORMATTER_IMPL(Q)
337 }
338
339