GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
130 of 148, 0 excluded
87.8%
Functions:
32 of 32, 0 excluded
100.0%
Branches:
54 of 118, 0 excluded
45.8%

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