Monado OpenXR Runtime
Loading...
Searching...
No Matches
lm_rotations.inl
Go to the documentation of this file.
1// Copyright 2022, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Autodiff-safe rotations for Levenberg-Marquardt kinematic optimizer.
6 * @author Moshi Turner <moshiturner@protonmail.com>
7 * @ingroup tracking
8 */
9
10
11#pragma once
12#include <algorithm>
13#include <cmath>
14#include <limits>
15#include "assert.h"
16#include "float.h"
17#include "lm_defines.hpp"
18
19#ifdef XRT_OS_LINUX
20#define likely(x) __builtin_expect((x), 1)
21#define unlikely(x) __builtin_expect((x), 0)
22#else
23#define likely(x) x
24#define unlikely(x) x
25#endif
26
27namespace xrt::tracking::hand::mercury::lm {
28
29// For debugging.
30#if 0
31#include <iostream>
32#define assert_quat_length_1(q) \
33 { \
34 const T scale = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]; \
35 if (abs(scale - T(1.0)) > 0.001) { \
36 std::cout << "Length bad! " << scale << std::endl; \
37 assert(false); \
38 }; \
39 }
40#else
41#define assert_quat_length_1(q)
42#endif
43
45
46
47template <typename T>
48inline void
49CurlToQuaternion(const T &theta, Quat<T> &result)
50{
51 const T theta_squared = theta * theta;
52
53 // For points not at the origin, the full conversion is numerically stable.
54 if (likely(theta_squared > T(0.0))) {
55 const T half_theta = theta * T(0.5);
56 const T k = sin(half_theta) / theta;
57 result.w = cos(half_theta);
58 result.x = theta * k;
59 result.y = T(0.0);
60 result.z = T(0.0);
61 } else {
62 // At the origin, dividing by 0 is probably bad. By approximating with a Taylor series,
63 // and truncating at one term, the value and first derivatives will be
64 // computed correctly when Jets are used.
65 const T k(0.5);
66 result.w = T(1.0);
67 result.x = theta * k;
68 result.y = T(0.0);
69 result.z = T(0.0);
70 }
71}
72
73template <typename T>
74inline void
75SwingToQuaternion(const Vec2<T> swing, Quat<T> &result)
76{
77
78 const T &a0 = swing.x;
79 const T &a1 = swing.y;
80 const T theta_squared = a0 * a0 + a1 * a1;
81
82 // For points not at the origin, the full conversion is numerically stable.
83 if (likely(theta_squared > T(0.0))) {
84 const T theta = sqrt(theta_squared);
85 const T half_theta = theta * T(0.5);
86 const T k = sin(half_theta) / theta;
87 result.w = cos(half_theta);
88 result.x = a0 * k;
89 result.y = a1 * k;
90 result.z = T(0);
91 } else {
92 // At the origin, sqrt() will produce NaN in the derivative since
93 // the argument is zero. By approximating with a Taylor series,
94 // and truncating at one term, the value and first derivatives will be
95 // computed correctly when Jets are used.
96 const T k(0.5);
97 result.w = T(1.0);
98 result.x = a0 * k;
99 result.y = a1 * k;
100 result.z = T(0);
101 }
102}
103
104
105// See
106// https://gitlab.freedesktop.org/slitcch/rotation_visualizer/-/blob/da5021d21600388b07c9c81000e866c4a2d015cb/lm_rotations_story.inl
107// for the derivation
108template <typename T>
109inline void
110SwingTwistToQuaternion(const Vec2<T> swing, const T twist, Quat<T> &result)
111{
112
113 T swing_x = swing.x;
114 T swing_y = swing.y;
115
116 T theta_squared_swing = swing_x * swing_x + swing_y * swing_y;
117
118 // So it turns out that we don't get any divisions by zero or nans in the
119 // differential part when twist is 0. I'm pretty sure we get lucky wrt. what cancels out
120
121 if (theta_squared_swing > T(0.0)) {
122 // theta_squared_swing is nonzero, so we the regular derived conversion.
123
124 T theta = sqrt(theta_squared_swing);
125
126 T half_theta = theta * T(0.5);
127
128 // the "other" theta
129 T half_twist = twist * T(0.5);
130
131 T cos_half_theta = cos(half_theta);
132 T cos_half_twist = cos(half_twist);
133
134 T sin_half_twist = sin(half_twist);
135
136 T sin_half_theta_over_theta = sin(half_theta) / theta;
137
138 result.w = cos_half_theta * cos_half_twist;
139
140 T x_part_1 = (swing_x * cos_half_twist * sin_half_theta_over_theta);
141 T x_part_2 = (swing_y * sin_half_twist * sin_half_theta_over_theta);
142
143 result.x = x_part_1 + x_part_2;
144
145 T y_part_1 = (swing_y * cos_half_twist * sin_half_theta_over_theta);
146 T y_part_2 = (swing_x * sin_half_twist * sin_half_theta_over_theta);
147
148 result.y = y_part_1 - y_part_2;
149
150 result.z = cos_half_theta * sin_half_twist;
151
152 } else {
153 // first: sin_half_theta/theta would be undefined, but
154 // the limit approaches 0.5.
155
156 // second: we only use theta to calculate sin_half_theta/theta
157 // and that function's derivative at theta=0 is 0, so this formulation is fine.
158
159 T half_twist = twist * T(0.5);
160
161 T cos_half_twist = cos(half_twist);
162
163 T sin_half_twist = sin(half_twist);
164
165 T sin_half_theta_over_theta = T(0.5);
166
167 // cos(0) is 1 so no cos_half_theta necessary
168 result.w = cos_half_twist;
169
170 T x_part_1 = (swing_x * cos_half_twist * sin_half_theta_over_theta);
171 T x_part_2 = (swing_y * sin_half_twist * sin_half_theta_over_theta);
172
173 result.x = x_part_1 + x_part_2;
174
175 T y_part_1 = (swing_y * cos_half_twist * sin_half_theta_over_theta);
176 T y_part_2 = (swing_x * sin_half_twist * sin_half_theta_over_theta);
177
178 result.y = y_part_1 - y_part_2;
179
180 result.z = sin_half_twist;
181 }
182}
183
184} // namespace xrt::tracking::hand::mercury::lm
Defines for Levenberg-Marquardt kinematic optimizer.
Autodiff-safe rotations for Levenberg-Marquardt kinematic optimizer.