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 &curl, Quat<T> &result)
50{
51 const T theta_squared = curl * curl;
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 theta = curl;
56 const T half_theta = curl * T(0.5);
57 const T k = sin(half_theta) / theta;
58 result.w = cos(half_theta);
59 result.x = curl * k;
60 result.y = T(0.0);
61 result.z = T(0.0);
62 } else {
63 // At the origin, dividing by 0 is probably bad. By approximating with a Taylor series,
64 // and truncating at one term, the value and first derivatives will be
65 // computed correctly when Jets are used.
66 const T k(0.5);
67 result.w = T(1.0);
68 result.x = curl * k;
69 result.y = T(0.0);
70 result.z = T(0.0);
71 }
72}
73
74template <typename T>
75inline void
76SwingToQuaternion(const Vec2<T> swing, Quat<T> &result)
77{
78
79 const T &a0 = swing.x;
80 const T &a1 = swing.y;
81 const T theta_squared = a0 * a0 + a1 * a1;
82
83 // For points not at the origin, the full conversion is numerically stable.
84 if (likely(theta_squared > T(0.0))) {
85 const T theta = sqrt(theta_squared);
86 const T half_theta = theta * T(0.5);
87 const T k = sin(half_theta) / theta;
88 result.w = cos(half_theta);
89 result.x = a0 * k;
90 result.y = a1 * k;
91 result.z = T(0);
92 } else {
93 // At the origin, sqrt() will produce NaN in the derivative since
94 // the argument is zero. By approximating with a Taylor series,
95 // and truncating at one term, the value and first derivatives will be
96 // computed correctly when Jets are used.
97 const T k(0.5);
98 result.w = T(1.0);
99 result.x = a0 * k;
100 result.y = a1 * k;
101 result.z = T(0);
102 }
103}
104
105
106// See
107// https://gitlab.freedesktop.org/slitcch/rotation_visualizer/-/blob/da5021d21600388b07c9c81000e866c4a2d015cb/lm_rotations_story.inl
108// for the derivation
109template <typename T>
110inline void
111SwingTwistToQuaternion(const Vec2<T> swing, const T twist, Quat<T> &result)
112{
113
114 T swing_x = swing.x;
115 T swing_y = swing.y;
116
117 T theta_squared_swing = swing_x * swing_x + swing_y * swing_y;
118
119 // So it turns out that we don't get any divisions by zero or nans in the
120 // differential part when twist is 0. I'm pretty sure we get lucky wrt. what cancels out
121
122 if (theta_squared_swing > T(0.0)) {
123 // theta_squared_swing is nonzero, so we the regular derived conversion.
124
125 T theta = sqrt(theta_squared_swing);
126
127 T half_theta = theta * T(0.5);
128
129 // the "other" theta
130 T half_twist = twist * T(0.5);
131
132 T cos_half_theta = cos(half_theta);
133 T cos_half_twist = cos(half_twist);
134
135 T sin_half_twist = sin(half_twist);
136
137 T sin_half_theta_over_theta = sin(half_theta) / theta;
138
139 result.w = cos_half_theta * cos_half_twist;
140
141 T x_part_1 = (swing_x * cos_half_twist * sin_half_theta_over_theta);
142 T x_part_2 = (swing_y * sin_half_twist * sin_half_theta_over_theta);
143
144 result.x = x_part_1 + x_part_2;
145
146 T y_part_1 = (swing_y * cos_half_twist * sin_half_theta_over_theta);
147 T y_part_2 = (swing_x * sin_half_twist * sin_half_theta_over_theta);
148
149 result.y = y_part_1 - y_part_2;
150
151 result.z = cos_half_theta * sin_half_twist;
152
153 } else {
154 // first: sin_half_theta/theta would be undefined, but
155 // the limit approaches 0.5.
156
157 // second: we only use theta to calculate sin_half_theta/theta
158 // and that function's derivative at theta=0 is 0, so this formulation is fine.
159
160 T half_twist = twist * T(0.5);
161
162 T cos_half_twist = cos(half_twist);
163
164 T sin_half_twist = sin(half_twist);
165
166 T sin_half_theta_over_theta = T(0.5);
167
168 // cos(0) is 1 so no cos_half_theta necessary
169 result.w = cos_half_twist;
170
171 T x_part_1 = (swing_x * cos_half_twist * sin_half_theta_over_theta);
172 T x_part_2 = (swing_y * sin_half_twist * sin_half_theta_over_theta);
173
174 result.x = x_part_1 + x_part_2;
175
176 T y_part_1 = (swing_y * cos_half_twist * sin_half_theta_over_theta);
177 T y_part_2 = (swing_x * sin_half_twist * sin_half_theta_over_theta);
178
179 result.y = y_part_1 - y_part_2;
180
181 result.z = sin_half_twist;
182 }
183}
184
185} // namespace xrt::tracking::hand::mercury::lm
Defines for Levenberg-Marquardt kinematic optimizer.
Autodiff-safe rotations for Levenberg-Marquardt kinematic optimizer.