The open source OpenXR runtime
1// Copyright 2022, Collabora, Inc.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Test for change-of-basis transformations between left-handed and right-handed coordinate sytems for
6 * quaternions.
7 * @author Moshi Turner <moshiturner@protonmail.com>
8 */
9#include "math/m_api.h"
10#include "xrt/xrt_defines.h"
11#include <util/u_worker.hpp>
12#include <util/u_logging.h>
13#include <math/m_space.h>
14#include <math/m_vec3.h>
15
16
17#include "catch_amalgamated.hpp"
18
19#include <random>
20
21
22#include "math/m_eigen_interop.hpp"
23#include <Eigen/Core>
24
25
26// https://stackoverflow.com/questions/28673777/convert-quaternion-from-right-handed-to-left-handed-coordinate-system
27
28
29// unity: x=0.3, w=0.95 . results in +40 rotation, which I think is wrong probably, should probably be -40
30// openxr, according to us:
31void
32log_quat(xrt_quat q)
33{
34 U_LOG_E("xyzw: %f %f %f %f", q.x, q.y, q.z, q.w);
35}
36
37float
38quat_difference(xrt_quat q1, xrt_quat q2)
39{
40 // https://math.stackexchange.com/a/90098
41 // d(q1,q2)=1−⟨q1,q2⟩2
42
43 float inner_product = (q1.w * q2.w) + (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z);
44 return 1.0 - (inner_product * inner_product);
45}
46
47xrt_quat
48random_quat()
49{
50 std::random_device dev;
51
52 auto mt = std::mt19937(dev());
53 auto rd = std::normal_distribution<float>(0, 1);
54
55 struct xrt_quat quat = {rd(mt), rd(mt), rd(mt), rd(mt)};
56
57 math_quat_normalize(&quat);
58 return quat;
59}
60
61//! https://stackoverflow.com/questions/28673777/convert-quaternion-from-right-handed-to-left-handed-coordinate-system
62// Same as zldtt_ori_right in lm_main.
63void
64slow_change_of_basis_lh_to_rh(xrt_quat *in, xrt_quat *out)
65{
66
67 xrt_vec3 x = XRT_VEC3_UNIT_X;
68 xrt_vec3 z = XRT_VEC3_UNIT_Z;
69
70 math_quat_rotate_vec3(in, &x, &x);
71 math_quat_rotate_vec3(in, &z, &z);
72
73 // This is a very squashed change-of-basis from left-handed coordinate systems to right-handed coordinate
74 // systems: you multiply everything by (-1 0 0) then negate the X axis.
75
76 x.y *= -1;
77 x.z *= -1;
78
79 z.x *= -1;
80
81 math_quat_from_plus_x_z(&x, &z, out);
82}
83
84void
85fast_change_of_basis_lh_to_rh(xrt_quat *in, xrt_quat *out)
86{
87 out->x = -in->x;
88 out->y = in->y;
89 out->z = in->z;
90 out->w = -in->w;
91}
92
93/*
94OpenXR is +X right, +Y up, -Z forward
95Unity is X+ Right, Y+ Up, Z+ Forward
96
97So we're not swapping axes, we're just flipping them. It's the same change of basis as "left hand" to "right hand"
98(indeed we chould have implemented left vs right in our optical hand tracking that way), just the "flip" is on the XY
99plane not the YZ plane.
100
101Vaguely based on `make_joint_at_matrix_right_hand` from ccdik_main but rotated
102*/
103
104void
105slow_change_of_basis_unity_to_oxr(xrt_quat *in, xrt_quat *out)
106{
107
108 Eigen::Quaternionf q;
109
110 q.w() = in->w;
111 q.x() = in->x;
112 q.y() = in->y;
113 q.z() = in->z;
114
115 Eigen::Matrix3f unity_rotation(q);
116
117 Eigen::Matrix3f mirror_unity_to_openxr = Eigen::Matrix3f::Identity();
118 mirror_unity_to_openxr(2, 2) = -1;
119
120 Eigen::Matrix3f intermediate = mirror_unity_to_openxr * unity_rotation;
121
122 intermediate(0, 2) *= -1;
123 intermediate(1, 2) *= -1;
124 intermediate(2, 2) *= -1;
125
126 Eigen::Quaternionf q_new;
127
128 q_new = intermediate;
129
130
131 out->w = q_new.w();
132 out->x = q_new.x();
133 out->y = q_new.y();
134 out->z = q_new.z();
135}
136
137void
138fast_change_of_basis_unity_to_oxr(xrt_quat *in, xrt_quat *out)
139{
140 out->x = in->x;
141 out->y = in->y;
142 out->z = -in->z;
143 out->w = -in->w;
144}
145
146TEST_CASE("QuaternionChangeOfBasis")
147
148{
149 U_LOG_E("LH to RH!");
150 for (int i = 0; i < 3; i++) {
151 xrt_quat q = random_quat();
152 xrt_quat q_prime;
153 xrt_quat q_prime_fast;
154 slow_change_of_basis_lh_to_rh(&q, &q_prime);
155 fast_change_of_basis_lh_to_rh(&q, &q_prime_fast);
156 log_quat(q);
157 log_quat(q_prime);
158 log_quat(q_prime_fast);
159 // xrt_quat factors;
160 // factors.w = q_prime.w / q.w;
161 // factors.x = q_prime.x / q.x;
162 // factors.y = q_prime.y / q.y;
163 // factors.z = q_prime.z / q.z;
164 // log_quat(factors);
165
166 CHECK(quat_difference(q_prime, q_prime_fast) < 0.01);
167 }
168
169 U_LOG_E("Unity to OpenXR!");
170 for (int i = 0; i < 3; i++) {
171 xrt_quat q = random_quat();
172 if (i == 0) {
173 q.x = 0.31;
174 q.y = 0;
175 q.z = 0;
176 q.w = 0.95;
177 math_quat_normalize(&q);
178 }
179 xrt_quat q_prime;
180 xrt_quat q_prime_fast;
181 slow_change_of_basis_unity_to_oxr(&q, &q_prime);
182 fast_change_of_basis_unity_to_oxr(&q, &q_prime_fast);
183 log_quat(q);
184 log_quat(q_prime);
185 log_quat(q_prime_fast);
186 // xrt_quat factors;
187 // factors.w = q_prime.w / q.w;
188 // factors.x = q_prime.x / q.x;
189 // factors.y = q_prime.y / q.y;
190 // factors.z = q_prime.z / q.z;
191 // log_quat(factors);
192
193 CHECK(quat_difference(q_prime, q_prime_fast) < 0.01);
194 }
195}