The open source OpenXR runtime
1// Copyright 2018-2020, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Input transform tests.
6 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
7 */
8
9#include "math/m_mathinclude.h"
10
11#include "catch_amalgamated.hpp"
12
13#include <xrt/xrt_defines.h>
14
15#include <oxr/oxr_input_transform.h>
16#include <oxr/oxr_logger.h>
17#include <oxr/oxr_objects.h>
18
19using Catch::Generators::values;
20
21TEST_CASE("input_transform")
22{
23 struct oxr_logger log;
24 oxr_log_init(&log, "test");
25 struct oxr_sink_logger slog = {};
26
27 struct oxr_input_transform *transforms = NULL;
28 size_t transform_count = 0;
29
30 oxr_input_value_tagged input = {};
31 oxr_input_value_tagged output = {};
32
33 SECTION("Float action")
34 {
35 XrActionType action_type = XR_ACTION_TYPE_FLOAT_INPUT;
36
37 SECTION("From Vec1 -1 to 1 identity")
38 {
39 input.type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
40
41 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "float_action",
42 "/mock_float", &transforms, &transform_count));
43
44 // Just identity
45 CHECK(transform_count == 1);
46 CHECK(transforms != nullptr);
47
48 SECTION("Roundtrip")
49 {
50 auto value = GENERATE(values({-1.f, -0.5f, 0.f, -0.f, 0.5f, 1.f}));
51 input.value.vec1.x = value;
52
53 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
54 CHECK(input.value.vec1.x == output.value.vec1.x);
55 }
56 }
57
58 SECTION("From Vec1 0 to 1 identity")
59 {
60 input.type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
61
62 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "float_action",
63 "/mock_float", &transforms, &transform_count));
64
65 // Just identity
66 CHECK(transform_count == 1);
67 CHECK(transforms != nullptr);
68
69 SECTION("Roundtrip")
70 {
71 auto value = GENERATE(values({0.f, -0.f, 0.5f, 1.f}));
72 input.value.vec1.x = value;
73
74 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
75 CHECK(input.value.vec1.x == output.value.vec1.x);
76 }
77 }
78
79 SECTION("From Vec2 input")
80 {
81 input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
82 input.value.vec2.x = -1;
83 input.value.vec2.y = 1;
84
85 SECTION("path component x")
86 {
87 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
88 "float_action", "/mock_vec2/x", &transforms,
89 &transform_count));
90
91 // A get-x
92 CHECK(transform_count == 1);
93 CHECK(transforms != nullptr);
94
95 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
96 CHECK(input.value.vec2.x == output.value.vec1.x);
97 }
98
99 SECTION("path component y")
100 {
101 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
102 "float_action", "/mock_vec2/y", &transforms,
103 &transform_count));
104
105 // A get-y
106 CHECK(transform_count == 1);
107 CHECK(transforms != nullptr);
108
109 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
110 CHECK(input.value.vec2.y == output.value.vec1.x);
111 }
112
113 SECTION("no component")
114 {
115 CHECK_FALSE(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
116 "float_action", "/mock_vec2", &transforms,
117 &transform_count));
118
119 // Shouldn't make a transform, not possible
120 CHECK(transform_count == 0);
121 CHECK(transforms == nullptr);
122
123 // shouldn't do anything, but shouldn't explode.
124 CHECK_FALSE(oxr_input_transform_process(transforms, transform_count, &input, &output));
125 }
126 }
127
128 SECTION("From bool input")
129 {
130 input.type = XRT_INPUT_TYPE_BOOLEAN;
131 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "float_action",
132 "/mock_bool", &transforms, &transform_count));
133
134 // A bool-to-float
135 CHECK(transform_count == 1);
136 CHECK(transforms != nullptr);
137
138 SECTION("False")
139 {
140 input.value.boolean = false;
141
142 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
143 CHECK(0.0f == output.value.vec1.x);
144 }
145
146 SECTION("True")
147 {
148 input.value.boolean = true;
149
150 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
151 CHECK(1.0f == output.value.vec1.x);
152 }
153 }
154 }
155
156 SECTION("Bool action")
157 {
158 XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT;
159 SECTION("From Bool identity")
160 {
161 input.type = XRT_INPUT_TYPE_BOOLEAN;
162
163 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "bool_action",
164 "/mock_bool", &transforms, &transform_count));
165 CHECK(transform_count == 1);
166 CHECK(transforms != nullptr);
167
168 SECTION("Roundtrip")
169 {
170 auto value = GENERATE(values({0, 1}));
171 input.value.boolean = bool(value);
172 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
173 CHECK(input.value.boolean == output.value.boolean);
174 }
175 }
176
177 SECTION("From Vec1 -1 to 1")
178 {
179 input.type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
180
181 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "bool_action",
182 "/mock_float", &transforms, &transform_count));
183 CHECK(transform_count == 1);
184 CHECK(transforms != nullptr);
185
186 SECTION("True")
187 {
188 auto value = GENERATE(values({0.5f, 1.f}));
189 input.value.vec1.x = value;
190
191 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
192 CHECK(output.value.boolean == true);
193 }
194
195 SECTION("False")
196 {
197 auto value = GENERATE(values({0.0f, -1.f}));
198 input.value.vec1.x = value;
199
200 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
201 CHECK(output.value.boolean == false);
202 }
203 }
204
205 SECTION("From Vec1 0 to 1")
206 {
207 input.type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
208
209 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "bool_action",
210 "/mock_float", &transforms, &transform_count));
211 // A bool to float
212 CHECK(transform_count == 1);
213 CHECK(transforms != nullptr);
214
215 SECTION("True")
216 {
217 auto value = GENERATE(values({0.95f, 1.f}));
218 input.value.vec1.x = value;
219
220 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
221 CHECK(output.value.boolean == true);
222 }
223
224 SECTION("False")
225 {
226 auto value = GENERATE(values({0.0f, 0.5f}));
227 input.value.vec1.x = value;
228
229 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
230 CHECK(output.value.boolean == false);
231 }
232 }
233
234 SECTION("From Vec2")
235 {
236 input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
237 input.value.vec2.x = -1;
238 input.value.vec2.y = 1;
239
240 SECTION("x")
241 {
242 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
243 "float_action", "/mock_vec2/x", &transforms,
244 &transform_count));
245 CHECK(transform_count == 2);
246 CHECK(transforms != nullptr);
247
248 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
249 CHECK(false == output.value.boolean);
250 }
251
252 SECTION("y")
253 {
254 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
255 "float_action", "/mock_vec2/y", &transforms,
256 &transform_count));
257 CHECK(transform_count == 2);
258 CHECK(transforms != nullptr);
259
260 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
261 CHECK(true == output.value.boolean);
262 }
263
264 SECTION("no component")
265 {
266 CHECK_FALSE(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
267 "float_action", "/mock", &transforms,
268 &transform_count));
269
270 // Shouldn't make a transform, not possible
271 CHECK(transform_count == 0);
272 CHECK(transforms == nullptr);
273
274 // shouldn't do anything, but shouldn't explode.
275 CHECK_FALSE(oxr_input_transform_process(transforms, transform_count, &input, &output));
276 }
277 }
278 }
279
280 SECTION("Pose action")
281 {
282 XrActionType action_type = XR_ACTION_TYPE_POSE_INPUT;
283
284 SECTION("From Pose identity")
285 {
286 input.type = XRT_INPUT_TYPE_POSE;
287 CHECK(oxr_input_transform_create_chain(&log, &slog, input.type, action_type, "pose_action",
288 "/mock_pose", &transforms, &transform_count));
289 // Identity, just so this binding doesn't get culled.
290 CHECK(transform_count == 1);
291 }
292
293 SECTION("From other input")
294 {
295 auto input_type = GENERATE(values({
296 XRT_INPUT_TYPE_BOOLEAN,
297 XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE,
298 XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE,
299 XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE,
300 XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE,
301 }));
302
303 CAPTURE(input_type);
304 input.type = input_type;
305
306 CHECK_FALSE(oxr_input_transform_create_chain(&log, &slog, input.type, action_type,
307 "pose_action", "/mock", &transforms,
308 &transform_count));
309
310 // not possible
311 CHECK(transform_count == 0);
312 CHECK(transforms == nullptr);
313 }
314 }
315
316 oxr_log_slog(&log, &slog);
317 oxr_input_transform_destroy(&transforms);
318 CHECK(NULL == transforms);
319}
320
321
322struct dpad_test_case
323{
324 float x;
325 float y;
326 enum oxr_dpad_region active_regions;
327};
328
329
330TEST_CASE("input_transform_dpad")
331{
332 struct oxr_logger log;
333 oxr_log_init(&log, "test");
334 struct oxr_sink_logger slog = {};
335
336 struct oxr_input_transform *transforms = NULL;
337 size_t transform_count = 0;
338
339 oxr_input_value_tagged input = {};
340 oxr_input_value_tagged output = {};
341
342 struct oxr_dpad_binding_modification *dpad_binding_modification = NULL;
343 enum xrt_input_type activation_input_type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
344 struct xrt_input activation_input = {};
345 enum oxr_dpad_region dpad_region = OXR_DPAD_REGION_UP;
346
347 SECTION("Default settings")
348 {
349 XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT;
350
351 SECTION("without an activation input")
352 {
353 input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
354
355 CHECK(oxr_input_transform_create_chain_dpad(
356 &log, &slog, input.type, action_type, "/mock_vec2/dpad_up", dpad_binding_modification,
357 dpad_region, activation_input_type, NULL, &transforms, &transform_count));
358 CHECK(transform_count == 1);
359 CHECK(transforms != nullptr);
360 CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD);
361
362
363 SECTION("up region is off in center")
364 {
365 input.value.vec2.x = 0.0f;
366 input.value.vec2.y = 0.0f;
367 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
368 CHECK(false == output.value.boolean);
369 }
370
371 SECTION("up region is on when pointing up")
372 {
373 input.value.vec2.x = 0.0f;
374 input.value.vec2.y = 1.0f;
375 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
376 CHECK(true == output.value.boolean);
377 }
378
379 struct dpad_test_case cases[9] = {
380 // obvious
381 {0.0f, 0.0f, OXR_DPAD_REGION_CENTER},
382 {0.0f, 1.0f, OXR_DPAD_REGION_UP},
383 {0.0f, -1.0f, OXR_DPAD_REGION_DOWN},
384 {-1.0f, 0.0f, OXR_DPAD_REGION_LEFT},
385 {1.0f, 0.0f, OXR_DPAD_REGION_RIGHT},
386 // boundary cases
387 {1.0f, 1.0f, OXR_DPAD_REGION_UP},
388 {-1.0f, -1.0f, OXR_DPAD_REGION_DOWN},
389 {-1.0f, 1.0f, OXR_DPAD_REGION_LEFT},
390 {1.0f, -1.0f, OXR_DPAD_REGION_RIGHT},
391 };
392
393 for (uint32_t i = 0; i < ARRAY_SIZE(cases); i++) {
394 DYNAMIC_SECTION("with (x, y) of (" << cases[i].x << ", " << cases[i].y << ")")
395 {
396 input.value.vec2.x = cases[i].x;
397 input.value.vec2.y = cases[i].y;
398 CHECK(
399 oxr_input_transform_process(transforms, transform_count, &input, &output));
400 CHECK(cases[i].active_regions == transforms[0].data.dpad_state.active_regions);
401 }
402 }
403 }
404 SECTION("with a boolean activation input")
405 {
406 input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
407 input.value.vec2.x = 0.0f;
408 input.value.vec2.y = 1.0f;
409
410 activation_input_type = XRT_INPUT_TYPE_BOOLEAN;
411
412 CHECK(oxr_input_transform_create_chain_dpad(
413 &log, &slog, input.type, action_type, "/mock_vec2/dpad_up", dpad_binding_modification,
414 dpad_region, activation_input_type, &activation_input, &transforms, &transform_count));
415 CHECK(transform_count == 1);
416 CHECK(transforms != nullptr);
417 CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD);
418
419 SECTION("when activation input is set to true")
420 {
421 activation_input.value.boolean = true;
422 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
423 CHECK(true == output.value.boolean);
424 }
425 SECTION("when activation input is set to false")
426 {
427 activation_input.value.boolean = false;
428 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
429 CHECK(false == output.value.boolean);
430 }
431 }
432 SECTION("with a float activation input")
433 {
434 input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
435 input.value.vec2.x = 0.0f;
436 input.value.vec2.y = 1.0f;
437
438 activation_input_type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
439
440 CHECK(oxr_input_transform_create_chain_dpad(
441 &log, &slog, input.type, action_type, "/mock_vec2/dpad_up", dpad_binding_modification,
442 dpad_region, activation_input_type, &activation_input, &transforms, &transform_count));
443 CHECK(transform_count == 1);
444 CHECK(transforms != nullptr);
445 CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD);
446
447 SECTION("when activation input is set to 1.0")
448 {
449 activation_input.value.vec1.x = 1.0f;
450 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
451 CHECK(true == output.value.boolean);
452 }
453 SECTION("when activation input is set to 0.0")
454 {
455 activation_input.value.vec1.x = 0.0f;
456 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
457 CHECK(false == output.value.boolean);
458 }
459 SECTION("when activation input varies")
460 {
461 activation_input.value.vec1.x = 0.45f;
462 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
463 CHECK(false == output.value.boolean);
464 activation_input.value.vec1.x = 0.6f;
465 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
466 CHECK(true == output.value.boolean);
467 activation_input.value.vec1.x = 0.45f;
468 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
469 CHECK(true == output.value.boolean);
470 activation_input.value.vec1.x = 0.35f;
471 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
472 CHECK(false == output.value.boolean);
473 }
474 }
475 }
476 SECTION("Sticky enabled")
477 {
478 XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT;
479
480 struct oxr_dpad_binding_modification dpad_binding_modification_val = {
481 XR_NULL_PATH, // XrPath binding, unused at this stage
482 {
483 0.5f, // float forceThreshold
484 0.4f, // float forceThresholdReleased
485 0.5f, // float centerRegion
486 (float)M_PI_2, // float wedgeAngle
487 true, // bool isSticky
488 }};
489 dpad_binding_modification = &dpad_binding_modification_val;
490
491 SECTION("without an activation input")
492 {
493 input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
494 input.value.vec2.x = 0.0f;
495 input.value.vec2.y = 1.0f;
496
497 CHECK(oxr_input_transform_create_chain_dpad(
498 &log, &slog, input.type, action_type, "/mock_vec2/dpad_up", dpad_binding_modification,
499 dpad_region, activation_input_type, NULL, &transforms, &transform_count));
500 CHECK(transform_count == 1);
501 CHECK(transforms != nullptr);
502 CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD);
503
504 SECTION("up region is off in center")
505 {
506 input.value.vec2.x = 0.0f;
507 input.value.vec2.y = 0.0f;
508 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
509 CHECK(false == output.value.boolean);
510 }
511
512 SECTION("up region is on when pointing up")
513 {
514 input.value.vec2.x = 0.0f;
515 input.value.vec2.y = 1.0f;
516 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
517 CHECK(true == output.value.boolean);
518 }
519 SECTION("up region is off when pointing down")
520 {
521 input.value.vec2.x = 0.0f;
522 input.value.vec2.y = -1.0f;
523 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
524 CHECK(false == output.value.boolean);
525 }
526
527 SECTION("up region stays on when stick moves clockwise to down")
528 {
529 input.value.vec2.x = 0.0f;
530 input.value.vec2.y = 1.0f;
531 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
532 CHECK(true == output.value.boolean);
533 input.value.vec2.x = 1.0f;
534 input.value.vec2.y = 0.0f;
535 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
536 CHECK(true == output.value.boolean);
537 input.value.vec2.x = 0.0f;
538 input.value.vec2.y = -1.0f;
539 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
540 CHECK(true == output.value.boolean);
541 input.value.vec2.x = 0.0f;
542 input.value.vec2.y = 0.0f;
543 CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output));
544 CHECK(false == output.value.boolean);
545 }
546 }
547 }
548
549 oxr_log_slog(&log, &slog);
550 oxr_input_transform_destroy(&transforms);
551 CHECK(NULL == transforms);
552}