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}