mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2024-12-28 18:46:18 +00:00
st/oxr: New input transform implementation, fixes conformance failures.
Includes unit tests for oxr_input_transform, add to CI This is a revised implementation using "variants" instead of "inheritance" in a linked-list.
This commit is contained in:
parent
e011e86fb9
commit
1a5d31b82b
|
@ -143,6 +143,7 @@ format-and-spellcheck:
|
|||
# List build options
|
||||
- grep "^XRT_" CMakeCache.txt
|
||||
- ninja
|
||||
- ctest --output-on-failure
|
||||
|
||||
# "Base" job for a Meson build
|
||||
.monado.base-job.build-meson:
|
||||
|
|
|
@ -68,6 +68,9 @@ else()
|
|||
find_package(OpenGL)
|
||||
endif()
|
||||
|
||||
# This one is named differently because that's what CTest uses
|
||||
option(BUILD_TESTING "Enable building of the test suite?" ON)
|
||||
|
||||
cmake_dependent_option(CMAKE_INTERPROCEDURAL_OPTIMIZATION "Enable inter-procedural (link-time) optimization" OFF "HAS_IPO" OFF)
|
||||
cmake_dependent_option(XRT_HAVE_WAYLAND "Enable Wayland support" ON "WAYLAND_FOUND AND WAYLAND_SCANNER_FOUND AND WAYLAND_PROTOCOLS_FOUND" OFF)
|
||||
cmake_dependent_option(XRT_HAVE_XLIB "Enable xlib support" ON "X11_FOUND" OFF)
|
||||
|
@ -168,3 +171,8 @@ endif()
|
|||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(doc)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
|
1
doc/changes/state_trackers/mr.379.md
Normal file
1
doc/changes/state_trackers/mr.379.md
Normal file
|
@ -0,0 +1 @@
|
|||
OpenXR: Transform input types in a somewhat flexible, composable way. Also, do conversion at sync time, and use the transformed values to evaluate if the input has changed, per the spec.
|
|
@ -42,6 +42,8 @@ set(OXR_SOURCE_FILES
|
|||
oxr_extension_support.h
|
||||
oxr_handle_base.c
|
||||
oxr_input.c
|
||||
oxr_input_transform.c
|
||||
oxr_input_transform.h
|
||||
oxr_instance.c
|
||||
oxr_logger.c
|
||||
oxr_logger.h
|
||||
|
@ -85,6 +87,8 @@ target_link_libraries(st_oxr PRIVATE
|
|||
Vulkan::Vulkan
|
||||
comp_client
|
||||
)
|
||||
target_include_directories(st_oxr PRIVATE
|
||||
target_include_directories(st_oxr
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
|
|
@ -55,6 +55,8 @@ lib_st_oxr = static_library(
|
|||
'oxr_extension_support.h',
|
||||
'oxr_handle_base.c',
|
||||
'oxr_input.c',
|
||||
'oxr_input_transform.c',
|
||||
'oxr_input_transform.h',
|
||||
'oxr_instance.c',
|
||||
'oxr_logger.c',
|
||||
'oxr_logger.h',
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "oxr_objects.h"
|
||||
#include "oxr_logger.h"
|
||||
#include "oxr_handle.h"
|
||||
#include "oxr_input_transform.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
@ -50,9 +51,6 @@ oxr_action_cache_update(struct oxr_logger *log,
|
|||
int64_t time,
|
||||
bool select);
|
||||
|
||||
/*!
|
||||
* @private @memberof oxr_action_attachment
|
||||
*/
|
||||
static void
|
||||
oxr_action_attachment_update(struct oxr_logger *log,
|
||||
struct oxr_session *sess,
|
||||
|
@ -82,6 +80,12 @@ oxr_action_bind_inputs(struct oxr_logger *log,
|
|||
static void
|
||||
oxr_action_cache_teardown(struct oxr_action_cache *cache)
|
||||
{
|
||||
// Clean up input transforms
|
||||
for (uint32_t i = 0; i < cache->num_inputs; i++) {
|
||||
struct oxr_action_input *action_input = &cache->inputs[i];
|
||||
oxr_input_transform_destroy(&(action_input->transforms));
|
||||
action_input->num_transforms = 0;
|
||||
}
|
||||
free(cache->inputs);
|
||||
cache->inputs = NULL;
|
||||
free(cache->outputs);
|
||||
|
@ -547,54 +551,6 @@ do_io_bindings(struct oxr_binding *b,
|
|||
return found;
|
||||
}
|
||||
|
||||
static bool
|
||||
ends_with(const char *str, const char *suffix)
|
||||
{
|
||||
int len = strlen(str);
|
||||
int suffix_len = strlen(suffix);
|
||||
|
||||
return (len >= suffix_len) &&
|
||||
(0 == strcmp(str + (len - suffix_len), suffix));
|
||||
}
|
||||
|
||||
static void
|
||||
oxr_action_cache_determine_redirect(struct oxr_logger *log,
|
||||
struct oxr_session *sess,
|
||||
struct oxr_action *act,
|
||||
struct oxr_action_cache *cache,
|
||||
XrPath bound_path)
|
||||
{
|
||||
|
||||
cache->redirect = INPUT_REDIRECT_DEFAULT;
|
||||
|
||||
struct oxr_action_input *input = &cache->inputs[0];
|
||||
if (input == NULL)
|
||||
return;
|
||||
|
||||
const char *str;
|
||||
size_t length;
|
||||
oxr_path_get_string(log, sess->sys->inst, bound_path, &str, &length);
|
||||
|
||||
enum xrt_input_type t = XRT_GET_INPUT_TYPE(input->input->name);
|
||||
|
||||
// trackpad/thumbstick data is kept in vec2f values.
|
||||
// When a float action binds to ../trackpad/x or ../thumbstick/y, store
|
||||
// the information which vec2f component to read.
|
||||
if (act->data->action_type == XR_ACTION_TYPE_FLOAT_INPUT &&
|
||||
t == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE) {
|
||||
if (ends_with(str, "/x")) {
|
||||
cache->redirect = INPUT_REDIRECT_VEC2_X_TO_VEC1;
|
||||
} else if (ends_with(str, "/y")) {
|
||||
cache->redirect = INPUT_REDIRECT_VEC2_Y_TO_VEC1;
|
||||
} else {
|
||||
oxr_log(log,
|
||||
"No rule to get float from vec2f for action "
|
||||
"%s, binding %s\n",
|
||||
act->data->name, str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static XrPath
|
||||
get_matched_xrpath(struct oxr_binding *b, struct oxr_action *act)
|
||||
{
|
||||
|
@ -732,7 +688,8 @@ oxr_action_attachment_bind(struct oxr_logger *log,
|
|||
|
||||
if (act_ref->sub_paths.user || act_ref->sub_paths.any) {
|
||||
#if 0
|
||||
oxr_action_bind_inputs(log, slog, sess, act, &act_attached->user, user,
|
||||
oxr_action_bind_inputs(log, &slog, sess, act,
|
||||
&act_attached->user, user,
|
||||
OXR_SUB_ACTION_PATH_USER);
|
||||
#endif
|
||||
}
|
||||
|
@ -791,6 +748,11 @@ oxr_action_cache_stop_output(struct oxr_logger *log,
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Called during xrSyncActions.
|
||||
*
|
||||
* @private @memberof oxr_action_cache
|
||||
*/
|
||||
static void
|
||||
oxr_action_cache_update(struct oxr_logger *log,
|
||||
struct oxr_session *sess,
|
||||
|
@ -832,34 +794,50 @@ oxr_action_cache_update(struct oxr_logger *log,
|
|||
cache->current.active = true;
|
||||
|
||||
/*!
|
||||
* @todo Combine multiple sources for a single subaction path
|
||||
* and convert type as required.
|
||||
* @todo Combine multiple sources for a single subaction path.
|
||||
*/
|
||||
|
||||
struct xrt_input *input = cache->inputs[0].input;
|
||||
struct oxr_action_input *action_input = &(cache->inputs[0]);
|
||||
struct xrt_input *input = action_input->input;
|
||||
struct oxr_input_value_tagged raw_input = {
|
||||
.type = XRT_GET_INPUT_TYPE(input->name),
|
||||
.value = input->value,
|
||||
};
|
||||
struct oxr_input_value_tagged transformed = {0};
|
||||
if (!oxr_input_transform_process(action_input->transforms,
|
||||
action_input->num_transforms,
|
||||
&raw_input, &transformed)) {
|
||||
// We couldn't transform, how strange. Reset all state.
|
||||
// At this level we don't know what action this is, etc.
|
||||
// so a warning message isn't very helpful.
|
||||
U_ZERO(&cache->current);
|
||||
return;
|
||||
}
|
||||
int64_t timestamp = input->timestamp;
|
||||
bool changed = false;
|
||||
switch (XRT_GET_INPUT_TYPE(input->name)) {
|
||||
switch (transformed.type) {
|
||||
case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE:
|
||||
case XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE: {
|
||||
changed = (input->value.vec1.x != last.value.vec1.x);
|
||||
cache->current.value = input->value;
|
||||
changed =
|
||||
(transformed.value.vec1.x != last.value.vec1.x);
|
||||
cache->current.value.vec1.x = transformed.value.vec1.x;
|
||||
break;
|
||||
}
|
||||
case XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE: {
|
||||
changed = (input->value.vec2.x != last.value.vec2.x) ||
|
||||
(input->value.vec2.y != last.value.vec2.y);
|
||||
cache->current.value = input->value;
|
||||
changed =
|
||||
(transformed.value.vec2.x != last.value.vec2.x) ||
|
||||
(transformed.value.vec2.y != last.value.vec2.y);
|
||||
cache->current.value.vec2.x = transformed.value.vec2.x;
|
||||
cache->current.value.vec2.y = transformed.value.vec2.y;
|
||||
break;
|
||||
}
|
||||
#if 0
|
||||
case XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE: {
|
||||
changed = (input->value.vec3.x != last.value.vec3.x) ||
|
||||
(input->value.vec3.y != last.value.vec3.y) ||
|
||||
(input->value.vec3.z != last.value.vec3.z);
|
||||
cache->current.value.vec3.x = input->value.vec3.x;
|
||||
cache->current.value.vec3.y = input->value.vec3.y;
|
||||
cache->current.value.vec3.z = input->value.vec3.z;
|
||||
changed = (transformed.value.vec3.x != last.vec3.x) ||
|
||||
(transformed.value.vec3.y != last.vec3.y) ||
|
||||
(transformed.value.vec3.z != last.vec3.z);
|
||||
cache->current.vec3.x = transformed.value.vec3.x;
|
||||
cache->current.vec3.y = transformed.value.vec3.y;
|
||||
cache->current.vec3.z = transformed.value.vec3.z;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
@ -915,6 +893,11 @@ oxr_action_cache_update(struct oxr_logger *log,
|
|||
} \
|
||||
}
|
||||
|
||||
/*!
|
||||
* Called during each xrSyncActions.
|
||||
*
|
||||
* @private @memberof oxr_action_attachment
|
||||
*/
|
||||
static void
|
||||
oxr_action_attachment_update(struct oxr_logger *log,
|
||||
struct oxr_session *sess,
|
||||
|
@ -1019,6 +1002,35 @@ oxr_action_attachment_update(struct oxr_logger *log,
|
|||
act_attached->any_state.active = true;
|
||||
}
|
||||
}
|
||||
/*!
|
||||
* Try to produce a transform chain to convert the available input into the
|
||||
* desired input type.
|
||||
*
|
||||
* Populates @p action_input->transforms and @p action_input->num_transforms on
|
||||
* success.
|
||||
*
|
||||
* @returns false if it could not, true if it could
|
||||
*/
|
||||
static bool
|
||||
oxr_action_populate_input_transform(struct oxr_logger *log,
|
||||
struct oxr_sink_logger *slog,
|
||||
struct oxr_session *sess,
|
||||
struct oxr_action *act,
|
||||
struct oxr_action_input *action_input,
|
||||
XrPath bound_path)
|
||||
{
|
||||
assert(action_input->transforms == NULL);
|
||||
assert(action_input->num_transforms == 0);
|
||||
const char *str;
|
||||
size_t length;
|
||||
oxr_path_get_string(log, sess->sys->inst, bound_path, &str, &length);
|
||||
|
||||
enum xrt_input_type t = XRT_GET_INPUT_TYPE(action_input->input->name);
|
||||
|
||||
return oxr_input_transform_create_chain(
|
||||
log, slog, t, act->data->action_type, act->data->name, str,
|
||||
&action_input->transforms, &action_input->num_transforms);
|
||||
}
|
||||
|
||||
static void
|
||||
oxr_action_bind_inputs(struct oxr_logger *log,
|
||||
|
@ -1034,7 +1046,7 @@ oxr_action_bind_inputs(struct oxr_logger *log,
|
|||
struct oxr_action_output outputs[16] = {0};
|
||||
uint32_t num_outputs = 0;
|
||||
|
||||
//! @todo Should this be asserted to be none-null?
|
||||
//! @todo Should this be asserted to be non-null?
|
||||
XrPath bound_path = XR_NULL_PATH;
|
||||
get_binding(log, slog, sess, act, profile, sub_path, inputs,
|
||||
&num_inputs, outputs, &num_outputs, &bound_path);
|
||||
|
@ -1046,6 +1058,19 @@ oxr_action_bind_inputs(struct oxr_logger *log,
|
|||
cache->inputs =
|
||||
U_TYPED_ARRAY_CALLOC(struct oxr_action_input, num_inputs);
|
||||
for (uint32_t i = 0; i < num_inputs; i++) {
|
||||
if (!oxr_action_populate_input_transform(
|
||||
log, slog, sess, act, &(inputs[i]),
|
||||
bound_path)) {
|
||||
/*!
|
||||
* @todo de-populate this element if we couldn't
|
||||
* get a transform?
|
||||
*/
|
||||
oxr_slog(
|
||||
slog,
|
||||
"Could not populate a transform for %s "
|
||||
"despite it being bound!\n",
|
||||
act->data->name);
|
||||
}
|
||||
cache->inputs[i] = inputs[i];
|
||||
}
|
||||
cache->num_inputs = num_inputs;
|
||||
|
@ -1060,8 +1085,6 @@ oxr_action_bind_inputs(struct oxr_logger *log,
|
|||
}
|
||||
cache->num_outputs = num_outputs;
|
||||
}
|
||||
|
||||
oxr_action_cache_determine_redirect(log, sess, act, cache, bound_path);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1303,40 +1326,29 @@ oxr_action_sync_data(struct oxr_logger *log,
|
|||
|
||||
static void
|
||||
get_state_from_state_bool(struct oxr_action_state *state,
|
||||
XrActionStateBoolean *data,
|
||||
enum xrt_source_value_redirect redirect)
|
||||
XrActionStateBoolean *data)
|
||||
{
|
||||
data->currentState = state->value.boolean;
|
||||
data->lastChangeTime = state->timestamp;
|
||||
data->changedSinceLastSync = state->changed;
|
||||
data->isActive = XR_TRUE;
|
||||
data->isActive = state->active;
|
||||
//! @todo
|
||||
// data->isActive = XR_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
get_state_from_state_vec1(struct oxr_action_state *state,
|
||||
XrActionStateFloat *data,
|
||||
enum xrt_source_value_redirect redirect)
|
||||
XrActionStateFloat *data)
|
||||
{
|
||||
switch (redirect) {
|
||||
case INPUT_REDIRECT_VEC2_X_TO_VEC1:
|
||||
data->currentState = state->value.vec2.x;
|
||||
break;
|
||||
case INPUT_REDIRECT_VEC2_Y_TO_VEC1:
|
||||
data->currentState = state->value.vec2.y;
|
||||
break;
|
||||
case INPUT_REDIRECT_DEFAULT:
|
||||
default: data->currentState = state->value.vec1.x; break;
|
||||
}
|
||||
|
||||
data->currentState = state->value.vec1.x;
|
||||
data->lastChangeTime = state->timestamp;
|
||||
data->changedSinceLastSync = state->changed;
|
||||
data->isActive = XR_TRUE;
|
||||
data->isActive = state->active;
|
||||
}
|
||||
|
||||
static void
|
||||
get_state_from_state_vec2(struct oxr_action_state *state,
|
||||
XrActionStateVector2f *data,
|
||||
enum xrt_source_value_redirect redirect)
|
||||
XrActionStateVector2f *data)
|
||||
{
|
||||
data->currentState.x = state->value.vec2.x;
|
||||
data->currentState.y = state->value.vec2.y;
|
||||
|
@ -1347,30 +1359,27 @@ get_state_from_state_vec2(struct oxr_action_state *state,
|
|||
|
||||
#define OXR_ACTION_GET_FILLER(TYPE) \
|
||||
if (sub_paths.any && act_attached->any_state.active) { \
|
||||
get_state_from_state_##TYPE(&act_attached->any_state, data, \
|
||||
INPUT_REDIRECT_DEFAULT); \
|
||||
get_state_from_state_##TYPE(&act_attached->any_state, data); \
|
||||
} \
|
||||
if (sub_paths.user && act_attached->user.current.active) { \
|
||||
get_state_from_state_##TYPE(&act_attached->user.current, data, \
|
||||
act_attached->user.redirect); \
|
||||
get_state_from_state_##TYPE(&act_attached->user.current, \
|
||||
data); \
|
||||
} \
|
||||
if (sub_paths.head && act_attached->head.current.active) { \
|
||||
get_state_from_state_##TYPE(&act_attached->head.current, data, \
|
||||
act_attached->head.redirect); \
|
||||
get_state_from_state_##TYPE(&act_attached->head.current, \
|
||||
data); \
|
||||
} \
|
||||
if (sub_paths.left && act_attached->left.current.active) { \
|
||||
get_state_from_state_##TYPE(&act_attached->left.current, data, \
|
||||
act_attached->left.redirect); \
|
||||
get_state_from_state_##TYPE(&act_attached->left.current, \
|
||||
data); \
|
||||
} \
|
||||
if (sub_paths.right && act_attached->right.current.active) { \
|
||||
get_state_from_state_##TYPE(&act_attached->right.current, \
|
||||
data, \
|
||||
act_attached->right.redirect); \
|
||||
data); \
|
||||
} \
|
||||
if (sub_paths.gamepad && act_attached->gamepad.current.active) { \
|
||||
get_state_from_state_##TYPE(&act_attached->gamepad.current, \
|
||||
data, \
|
||||
act_attached->gamepad.redirect); \
|
||||
data); \
|
||||
}
|
||||
|
||||
|
||||
|
|
329
src/xrt/state_trackers/oxr/oxr_input_transform.c
Normal file
329
src/xrt/state_trackers/oxr/oxr_input_transform.c
Normal file
|
@ -0,0 +1,329 @@
|
|||
// Copyright 2018-2020, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Handles transformation/filtering of input data.
|
||||
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
|
||||
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||||
* @ingroup oxr_input_transform
|
||||
*/
|
||||
|
||||
#include "oxr_input_transform.h"
|
||||
#include "oxr_logger.h"
|
||||
#include "oxr_objects.h"
|
||||
|
||||
#include "util/u_misc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*!
|
||||
* Arbitrary but larger than required.
|
||||
*/
|
||||
#define OXR_MAX_INPUT_TRANSFORMS 5
|
||||
|
||||
void
|
||||
oxr_input_transform_destroy(struct oxr_input_transform **transform_ptr)
|
||||
{
|
||||
struct oxr_input_transform *xform = *transform_ptr;
|
||||
if (xform == NULL) {
|
||||
return;
|
||||
}
|
||||
free(xform);
|
||||
*transform_ptr = NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_init_root(struct oxr_input_transform *transform,
|
||||
enum xrt_input_type input_type)
|
||||
{
|
||||
assert(transform != NULL);
|
||||
U_ZERO(transform);
|
||||
transform->type = INPUT_TRANSFORM_IDENTITY;
|
||||
transform->result_type = input_type;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_init_vec2_get_x(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent)
|
||||
{
|
||||
assert(transform != NULL);
|
||||
assert(parent != NULL);
|
||||
assert(parent->result_type == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE);
|
||||
|
||||
U_ZERO(transform);
|
||||
transform->type = INPUT_TRANSFORM_VEC2_GET_X;
|
||||
transform->result_type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_init_vec2_get_y(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent)
|
||||
{
|
||||
assert(transform != NULL);
|
||||
assert(parent != NULL);
|
||||
assert(parent->result_type == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE);
|
||||
|
||||
U_ZERO(transform);
|
||||
transform->type = INPUT_TRANSFORM_VEC2_GET_Y;
|
||||
transform->result_type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_init_threshold(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent,
|
||||
float threshold,
|
||||
bool invert)
|
||||
{
|
||||
assert(transform != NULL);
|
||||
assert(parent != NULL);
|
||||
assert((parent->result_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) ||
|
||||
(parent->result_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE));
|
||||
|
||||
U_ZERO(transform);
|
||||
transform->type = INPUT_TRANSFORM_THRESHOLD;
|
||||
transform->result_type = XRT_INPUT_TYPE_BOOLEAN;
|
||||
transform->data.threshold.threshold = threshold;
|
||||
transform->data.threshold.invert = invert;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_init_bool_to_vec1(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent,
|
||||
enum xrt_input_type result_type,
|
||||
float true_val,
|
||||
float false_val)
|
||||
{
|
||||
assert(transform != NULL);
|
||||
assert(parent != NULL);
|
||||
assert(parent->result_type == XRT_INPUT_TYPE_BOOLEAN);
|
||||
assert((result_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) ||
|
||||
(result_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE));
|
||||
|
||||
U_ZERO(transform);
|
||||
transform->type = INPUT_TRANSFORM_BOOL_TO_VEC1;
|
||||
transform->result_type = result_type;
|
||||
transform->data.bool_to_vec1.true_val = true_val;
|
||||
transform->data.bool_to_vec1.false_val = false_val;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_process(const struct oxr_input_transform *transform,
|
||||
size_t num_transforms,
|
||||
const struct oxr_input_value_tagged *input,
|
||||
struct oxr_input_value_tagged *out)
|
||||
{
|
||||
if (transform == NULL) {
|
||||
return false;
|
||||
}
|
||||
struct oxr_input_value_tagged data = *input;
|
||||
for (size_t i = 0; i < num_transforms; ++i) {
|
||||
const struct oxr_input_transform *xform = &(transform[i]);
|
||||
switch (xform->type) {
|
||||
case INPUT_TRANSFORM_IDENTITY:
|
||||
// do nothing
|
||||
break;
|
||||
case INPUT_TRANSFORM_VEC2_GET_X:
|
||||
data.value.vec1.x = data.value.vec2.x;
|
||||
break;
|
||||
case INPUT_TRANSFORM_VEC2_GET_Y:
|
||||
data.value.vec1.x = data.value.vec2.y;
|
||||
break;
|
||||
case INPUT_TRANSFORM_THRESHOLD: {
|
||||
bool temp =
|
||||
data.value.vec1.x > xform->data.threshold.threshold;
|
||||
if (xform->data.threshold.invert) {
|
||||
temp = !temp;
|
||||
}
|
||||
data.value.boolean = temp;
|
||||
break;
|
||||
}
|
||||
case INPUT_TRANSFORM_BOOL_TO_VEC1: {
|
||||
data.value.vec1.x =
|
||||
data.value.boolean
|
||||
? xform->data.bool_to_vec1.true_val
|
||||
: xform->data.bool_to_vec1.false_val;
|
||||
break;
|
||||
}
|
||||
case INPUT_TRANSFORM_INVALID:
|
||||
default: return false;
|
||||
}
|
||||
// Update the data type tag
|
||||
data.type = xform->result_type;
|
||||
}
|
||||
*out = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ends_with(const char *str, const char *suffix)
|
||||
{
|
||||
int len = strlen(str);
|
||||
int suffix_len = strlen(suffix);
|
||||
|
||||
return (len >= suffix_len) &&
|
||||
(0 == strcmp(str + (len - suffix_len), suffix));
|
||||
}
|
||||
static inline bool
|
||||
input_is_float(enum xrt_input_type input_type)
|
||||
{
|
||||
return (input_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) ||
|
||||
(input_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE);
|
||||
}
|
||||
static inline uint8_t
|
||||
input_dim(enum xrt_input_type input_type)
|
||||
{
|
||||
switch (input_type) {
|
||||
case XRT_INPUT_TYPE_BOOLEAN:
|
||||
case XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE:
|
||||
case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE: return 1;
|
||||
case XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE: return 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
oxr_type_matches_xrt(enum xrt_input_type input_type, XrActionType result_type)
|
||||
{
|
||||
switch (result_type) {
|
||||
case XR_ACTION_TYPE_BOOLEAN_INPUT:
|
||||
return input_type == XRT_INPUT_TYPE_BOOLEAN;
|
||||
case XR_ACTION_TYPE_FLOAT_INPUT: return input_is_float(input_type);
|
||||
case XR_ACTION_TYPE_VECTOR2F_INPUT:
|
||||
return input_type == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
extend_transform_array(struct oxr_logger *log,
|
||||
struct oxr_sink_logger *slog,
|
||||
struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent,
|
||||
XrActionType result_type,
|
||||
const char *bound_path_string)
|
||||
{
|
||||
enum xrt_input_type input_type = parent->result_type;
|
||||
if (input_dim(input_type) == 2 &&
|
||||
result_type != XR_ACTION_TYPE_VECTOR2F_INPUT) {
|
||||
// reduce dimension
|
||||
if (ends_with(bound_path_string, "/x")) {
|
||||
oxr_slog(slog, "Adding transform: get x of Vec2\n");
|
||||
return oxr_input_transform_init_vec2_get_x(transform,
|
||||
parent);
|
||||
}
|
||||
if (ends_with(bound_path_string, "/y")) {
|
||||
oxr_slog(slog, "Adding transform: get y of Vec2\n");
|
||||
return oxr_input_transform_init_vec2_get_y(transform,
|
||||
parent);
|
||||
}
|
||||
oxr_log(log, "No rule to get float from vec2f for binding %s\n",
|
||||
bound_path_string);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (input_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE &&
|
||||
result_type == XR_ACTION_TYPE_BOOLEAN_INPUT) {
|
||||
// 0.2 is for a little deadband around the center.
|
||||
oxr_slog(slog, "Adding transform: threshold [-1, 1] float\n");
|
||||
return oxr_input_transform_init_threshold(transform, parent,
|
||||
0.2f, false);
|
||||
}
|
||||
if (input_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE &&
|
||||
result_type == XR_ACTION_TYPE_BOOLEAN_INPUT) {
|
||||
// Need it pressed nearly all the way
|
||||
oxr_slog(slog, "Adding transform: threshold [0, 1] float\n");
|
||||
return oxr_input_transform_init_threshold(transform, parent,
|
||||
0.7f, false);
|
||||
}
|
||||
if (input_type == XRT_INPUT_TYPE_BOOLEAN &&
|
||||
result_type == XR_ACTION_TYPE_FLOAT_INPUT) {
|
||||
// this conversion is in the spec
|
||||
oxr_slog(slog, "Adding transform: bool to float\n");
|
||||
return oxr_input_transform_init_bool_to_vec1(
|
||||
transform, parent, XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, 1.f,
|
||||
0.f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct oxr_input_transform *
|
||||
oxr_input_transform_clone_chain(struct oxr_input_transform *transforms,
|
||||
size_t num_transforms)
|
||||
{
|
||||
struct oxr_input_transform *ret =
|
||||
U_TYPED_ARRAY_CALLOC(struct oxr_input_transform, num_transforms);
|
||||
memcpy(ret, transforms, sizeof(*ret) * num_transforms);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
oxr_input_transform_create_chain(struct oxr_logger *log,
|
||||
struct oxr_sink_logger *slog,
|
||||
enum xrt_input_type input_type,
|
||||
XrActionType result_type,
|
||||
const char *action_name,
|
||||
const char *bound_path_string,
|
||||
struct oxr_input_transform **out_transforms,
|
||||
size_t *out_num_transforms)
|
||||
{
|
||||
struct oxr_input_transform chain[OXR_MAX_INPUT_TRANSFORMS] = {0};
|
||||
|
||||
struct oxr_input_transform *current_xform = &(chain[0]);
|
||||
if (!oxr_input_transform_init_root(current_xform, input_type)) {
|
||||
*out_num_transforms = 0;
|
||||
*out_transforms = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t num_transforms = 1;
|
||||
if (result_type == XR_ACTION_TYPE_POSE_INPUT &&
|
||||
input_type == XRT_INPUT_TYPE_POSE) {
|
||||
// No transform needed, just return identity to keep this alive.
|
||||
*out_num_transforms = num_transforms;
|
||||
*out_transforms =
|
||||
oxr_input_transform_clone_chain(chain, num_transforms);
|
||||
return true;
|
||||
}
|
||||
while (!oxr_type_matches_xrt(current_xform->result_type, result_type)) {
|
||||
if (num_transforms >= OXR_MAX_INPUT_TRANSFORMS) {
|
||||
// Couldn't finish the transform to the desired type.
|
||||
oxr_log(
|
||||
log,
|
||||
"Seem to have gotten into a loop, trying to make a "
|
||||
"rule to transform action %s, binding %s\n",
|
||||
action_name, bound_path_string);
|
||||
*out_num_transforms = 0;
|
||||
*out_transforms = NULL;
|
||||
return false;
|
||||
}
|
||||
struct oxr_input_transform *new_xform =
|
||||
&(chain[num_transforms]);
|
||||
if (!extend_transform_array(log, slog, new_xform, current_xform,
|
||||
result_type, bound_path_string)) {
|
||||
// Couldn't finish the transform to the desired type.
|
||||
oxr_log(log,
|
||||
"No rule to transform action %s, binding %s\n",
|
||||
action_name, bound_path_string);
|
||||
*out_num_transforms = 0;
|
||||
*out_transforms = NULL;
|
||||
return false;
|
||||
}
|
||||
num_transforms++;
|
||||
current_xform = new_xform;
|
||||
}
|
||||
*out_num_transforms = num_transforms;
|
||||
*out_transforms =
|
||||
oxr_input_transform_clone_chain(chain, num_transforms);
|
||||
return true;
|
||||
}
|
314
src/xrt/state_trackers/oxr/oxr_input_transform.h
Normal file
314
src/xrt/state_trackers/oxr/oxr_input_transform.h
Normal file
|
@ -0,0 +1,314 @@
|
|||
// Copyright 2018-2020, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Defines ways of performing (possibly multi-step) conversions of input
|
||||
* data.
|
||||
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
|
||||
* @ingroup oxr_input_transform
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "xrt/xrt_device.h"
|
||||
|
||||
// we need no platform-specific defines from OpenXR.
|
||||
#include "openxr/openxr.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct oxr_logger;
|
||||
struct oxr_sink_logger;
|
||||
struct oxr_action;
|
||||
struct oxr_action_cache;
|
||||
|
||||
/*!
|
||||
* @defgroup oxr_input_transform OpenXR input transformation
|
||||
*
|
||||
*
|
||||
* @ingroup oxr
|
||||
* @{
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Tag for the input transform
|
||||
*
|
||||
* @see oxr_input_transform
|
||||
*/
|
||||
enum oxr_input_transform_type
|
||||
{
|
||||
/*!
|
||||
* Invalid value, so that zero-initialization without further assignment
|
||||
* is caught.
|
||||
*/
|
||||
INPUT_TRANSFORM_INVALID = 0,
|
||||
|
||||
/*!
|
||||
* Do not modify the input.
|
||||
*
|
||||
* This is only used as the root/head transform, to set the initial
|
||||
* type.
|
||||
*/
|
||||
INPUT_TRANSFORM_IDENTITY,
|
||||
|
||||
/*!
|
||||
* Get the X component of a 2D float input of any range.
|
||||
*/
|
||||
INPUT_TRANSFORM_VEC2_GET_X,
|
||||
|
||||
/*!
|
||||
* Get the Y component of a 2D float input of any range.
|
||||
*/
|
||||
INPUT_TRANSFORM_VEC2_GET_Y,
|
||||
|
||||
/*!
|
||||
* Apply a threshold to any 1D float input to make a bool.
|
||||
*
|
||||
* This transform type has data:
|
||||
*
|
||||
* @see oxr_input_transform_threshold_data
|
||||
*/
|
||||
INPUT_TRANSFORM_THRESHOLD,
|
||||
|
||||
/*!
|
||||
* Convert a bool to some range of 1D float input.
|
||||
*
|
||||
* This transform type has data:
|
||||
*
|
||||
* @see oxr_input_transform_bool_to_vec1_data
|
||||
*/
|
||||
INPUT_TRANSFORM_BOOL_TO_VEC1,
|
||||
};
|
||||
|
||||
struct oxr_input_transform;
|
||||
/*!
|
||||
* Data required for INPUT_TRANSFORM_THRESHOLD
|
||||
*/
|
||||
struct oxr_input_transform_threshold_data
|
||||
{
|
||||
//! The "greater-than" threshold value
|
||||
float threshold;
|
||||
|
||||
//! If true, values above threshold are false instead of
|
||||
//! true
|
||||
bool invert;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Data required for INPUT_TRANSFORM_BOOL_TO_VEC1
|
||||
* @see oxr_input_transform
|
||||
* @see INPUT_TRANSFORM_BOOL_TO_VEC1
|
||||
*/
|
||||
struct oxr_input_transform_bool_to_vec1_data
|
||||
{
|
||||
//! Value produced if bool is true.
|
||||
float true_val;
|
||||
|
||||
//! Value produced if bool is false.
|
||||
float false_val;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Variant type for input transforms.
|
||||
*
|
||||
* Some values for @p type do not have any additional data in the union
|
||||
*/
|
||||
struct oxr_input_transform
|
||||
{
|
||||
/*!
|
||||
* The type of this transform.
|
||||
*
|
||||
* Some values imply that a member of oxr_input_transform::data is
|
||||
* populated.
|
||||
*/
|
||||
enum oxr_input_transform_type type;
|
||||
|
||||
//! The type output by this transform.
|
||||
enum xrt_input_type result_type;
|
||||
|
||||
union {
|
||||
/*!
|
||||
* Populated when oxr_input_transform::type is
|
||||
* INPUT_TRANSFORM_THRESHOLD
|
||||
*/
|
||||
struct oxr_input_transform_threshold_data threshold;
|
||||
/*!
|
||||
* Populated when oxr_input_transform::type is
|
||||
* INPUT_TRANSFORM_BOOL_TO_VEC1
|
||||
*/
|
||||
struct oxr_input_transform_bool_to_vec1_data bool_to_vec1;
|
||||
} data;
|
||||
};
|
||||
|
||||
/*!
|
||||
* An input value enum with the associated tag required to interpret it.
|
||||
*/
|
||||
struct oxr_input_value_tagged
|
||||
{
|
||||
enum xrt_input_type type;
|
||||
union xrt_input_value value;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Destroy an array of input transforms.
|
||||
*
|
||||
* Performs null check and sets to NULL.
|
||||
*
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
void
|
||||
oxr_input_transform_destroy(struct oxr_input_transform **transform_ptr);
|
||||
|
||||
/*!
|
||||
* Apply an array of input transforms.
|
||||
*
|
||||
* @param[in] transforms An array of input transforms
|
||||
* @param[in] num_transforms The number of elements in @p transform
|
||||
* @param[in] input The input value and type
|
||||
* @param[out] out The transformed value and type
|
||||
*
|
||||
* @returns false if there was a type mismatch
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_process(const struct oxr_input_transform *transforms,
|
||||
size_t num_transforms,
|
||||
const struct oxr_input_value_tagged *input,
|
||||
struct oxr_input_value_tagged *out);
|
||||
|
||||
/*!
|
||||
* Allocate an identity transform serving as the root/head of the transform
|
||||
* chain.
|
||||
*
|
||||
* Usually called automatically by @ref oxr_input_transform_create_chain
|
||||
*
|
||||
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
|
||||
* initialize.
|
||||
* @param[in] input_type The native input type from the device
|
||||
*
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_init_root(struct oxr_input_transform *transform,
|
||||
const enum xrt_input_type input_type);
|
||||
|
||||
/*!
|
||||
* Allocate a transform to get the X component of a Vec2.
|
||||
*
|
||||
* Usually called automatically by @ref oxr_input_transform_create_chain
|
||||
*
|
||||
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
|
||||
* initialize.
|
||||
* @param[in] parent The preceding transform
|
||||
*
|
||||
* @pre parent->result_type is @ref XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE
|
||||
*
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_init_vec2_get_x(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent);
|
||||
|
||||
/*!
|
||||
* Allocate a transform to get the Y component of a Vec2.
|
||||
*
|
||||
* Usually called automatically by @ref oxr_input_transform_create_chain
|
||||
*
|
||||
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
|
||||
* initialize.
|
||||
* @param[in] parent The preceding transform
|
||||
*
|
||||
* @pre parent->result_type is @ref XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE
|
||||
*
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_init_vec2_get_y(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent);
|
||||
|
||||
/*!
|
||||
* Allocate a transform to threshold a float to a bool.
|
||||
*
|
||||
* Usually called automatically by @ref oxr_input_transform_create_chain
|
||||
*
|
||||
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
|
||||
* initialize.
|
||||
* @param[in] parent The preceding transform
|
||||
* @param[in] threshold Threshold value to use
|
||||
* @param[in] invert If true, condition is "value <= threshold" instead of
|
||||
* "value > threshold"
|
||||
*
|
||||
* @pre parent->result_type is @ref XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE or
|
||||
* @ref XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE
|
||||
*
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_init_threshold(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent,
|
||||
float threshold,
|
||||
bool invert);
|
||||
|
||||
/*!
|
||||
* Allocate a transform to turn a bool into an arbitrary 1D float.
|
||||
*
|
||||
* Usually called automatically by @ref oxr_input_transform_create_chain
|
||||
*
|
||||
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
|
||||
* initialize.
|
||||
* @param[in] parent The preceding transform
|
||||
* @param[in] result_type Either XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE or
|
||||
* XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE
|
||||
* @param[in] true_val Value to return when true
|
||||
* @param[in] false_val Value to return when false
|
||||
*
|
||||
* @pre parent->result_type is XRT_INPUT_TYPE_BOOLEAN
|
||||
*
|
||||
* @public @memberof oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_init_bool_to_vec1(struct oxr_input_transform *transform,
|
||||
const struct oxr_input_transform *parent,
|
||||
enum xrt_input_type result_type,
|
||||
float true_val,
|
||||
float false_val);
|
||||
|
||||
/*!
|
||||
* Create a transform array to convert @p input_type to @p result_type.
|
||||
*
|
||||
* @todo In the future, this should be configured using knowledge from the
|
||||
* device as well as user options.
|
||||
*
|
||||
* @param[in] log The logger
|
||||
* @param[in] slog The sink logger
|
||||
* @param[in] input_type The type of input received from the hardware
|
||||
* @param[in] result_type The type of input the application requested
|
||||
* @param[in] action_name The action name - used for error prints only
|
||||
* @param[in] bound_path_string The path name string that has been bound.
|
||||
* @param[out] out_transforms A pointer that will be populated with the output
|
||||
* array's address, or NULL.
|
||||
* @param[out] out_num_transforms Where to populate the array size
|
||||
* @return false if not possible
|
||||
*
|
||||
* @relates oxr_input_transform
|
||||
*/
|
||||
bool
|
||||
oxr_input_transform_create_chain(struct oxr_logger *log,
|
||||
struct oxr_sink_logger *slog,
|
||||
enum xrt_input_type input_type,
|
||||
XrActionType result_type,
|
||||
const char *action_name,
|
||||
const char *bound_path_string,
|
||||
struct oxr_input_transform **out_transforms,
|
||||
size_t *out_num_transforms);
|
||||
|
||||
/*!
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1400,7 +1400,8 @@ struct oxr_action_state
|
|||
};
|
||||
|
||||
/*!
|
||||
* A input action pair of a @ref xrt_input and a @ref xrt_device.
|
||||
* A input action pair of a @ref xrt_input and a @ref xrt_device, along with the
|
||||
* required transform.
|
||||
*
|
||||
* @see xrt_device
|
||||
* @see xrt_input
|
||||
|
@ -1409,6 +1410,8 @@ struct oxr_action_input
|
|||
{
|
||||
struct xrt_device *xdev;
|
||||
struct xrt_input *input;
|
||||
struct oxr_input_transform *transforms;
|
||||
size_t num_transforms;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -1424,7 +1427,10 @@ struct oxr_action_output
|
|||
};
|
||||
|
||||
/*!
|
||||
* A set of inputs for a single sub action path.
|
||||
* The set of inputs/outputs for a single sub-action path for an action.
|
||||
*
|
||||
* Each @ref oxr_action_attachment has one of these for every known sub-action
|
||||
* path in the spec. Many, or even most, will be "empty".
|
||||
*
|
||||
* @see oxr_action_attachment
|
||||
*/
|
||||
|
@ -1438,8 +1444,6 @@ struct oxr_action_cache
|
|||
int64_t stop_output_time;
|
||||
size_t num_outputs;
|
||||
struct oxr_action_output *outputs;
|
||||
|
||||
enum xrt_source_value_redirect redirect;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
|
19
tests/CMakeLists.txt
Normal file
19
tests/CMakeLists.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2018-2020, Collabora, Ltd. SPDX-License-Identifier: BSL-1.0
|
||||
|
||||
# Catch2 main test driver
|
||||
add_library(tests_main STATIC tests_main.cpp)
|
||||
target_link_libraries(tests_main PUBLIC xrt-external-catch2)
|
||||
if(ANDROID)
|
||||
target_link_libraries(tests_main PUBLIC log)
|
||||
endif()
|
||||
|
||||
|
||||
# Input transform test
|
||||
add_executable(tests_input_transform tests_input_transform.cpp)
|
||||
target_link_libraries(tests_input_transform PRIVATE tests_main)
|
||||
target_link_libraries(tests_input_transform PRIVATE
|
||||
st_oxr
|
||||
xrt-interfaces
|
||||
xrt-external-openxr
|
||||
aux_util)
|
||||
add_test(NAME input_transform COMMAND tests_input_transform --success)
|
272
tests/tests_input_transform.cpp
Normal file
272
tests/tests_input_transform.cpp
Normal file
|
@ -0,0 +1,272 @@
|
|||
// Copyright 2018-2020, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Input transform tests.
|
||||
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
|
||||
*/
|
||||
|
||||
#include "catch/catch.hpp"
|
||||
|
||||
#include <xrt/xrt_defines.h>
|
||||
|
||||
#include <oxr/oxr_input_transform.h>
|
||||
#include <oxr/oxr_logger.h>
|
||||
#include <oxr/oxr_objects.h>
|
||||
|
||||
using Catch::Generators::values;
|
||||
|
||||
TEST_CASE("input_transform") {
|
||||
struct oxr_logger log;
|
||||
oxr_log_init(&log, "test");
|
||||
struct oxr_sink_logger slog = {};
|
||||
|
||||
struct oxr_input_transform *transforms = NULL;
|
||||
size_t num_transforms = 0;
|
||||
|
||||
oxr_input_value_tagged input = {};
|
||||
oxr_input_value_tagged output = {};
|
||||
|
||||
SECTION("Float action") {
|
||||
XrActionType action_type = XR_ACTION_TYPE_FLOAT_INPUT;
|
||||
SECTION("From Vec1 -1 to 1 identity") {
|
||||
input.type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_float", &transforms, &num_transforms));
|
||||
|
||||
// Just identity
|
||||
CHECK(num_transforms == 1);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
SECTION("Roundtrip") {
|
||||
auto value =
|
||||
GENERATE(values({-1.f, -0.5f, 0.f, -0.f, 0.5f, 1.f}));
|
||||
input.value.vec1.x = value;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(input.value.vec1.x == output.value.vec1.x);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("From Vec1 0 to 1 identity") {
|
||||
input.type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_float", &transforms, &num_transforms));
|
||||
// Just identity
|
||||
CHECK(num_transforms == 1);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
SECTION("Roundtrip") {
|
||||
auto value = GENERATE(values({0.f, -0.f, 0.5f, 1.f}));
|
||||
input.value.vec1.x = value;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(input.value.vec1.x == output.value.vec1.x);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("From Vec2 input") {
|
||||
input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
|
||||
input.value.vec2.x = -1;
|
||||
input.value.vec2.y = 1;
|
||||
SECTION("path component x") {
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_vec2/x", &transforms, &num_transforms));
|
||||
// Identity and a get-x
|
||||
CHECK(num_transforms == 2);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(input.value.vec2.x == output.value.vec1.x);
|
||||
}
|
||||
SECTION("path component y") {
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_vec2/y", &transforms, &num_transforms));
|
||||
// Identity and a get-y
|
||||
CHECK(num_transforms == 2);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(input.value.vec2.y == output.value.vec1.x);
|
||||
}
|
||||
SECTION("no component") {
|
||||
CHECK_FALSE(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_vec2", &transforms, &num_transforms));
|
||||
// Shouldn't make a transform, not possible
|
||||
CHECK(num_transforms == 0);
|
||||
CHECK(transforms == nullptr);
|
||||
|
||||
// shouldn't do anything, but shouldn't explode.
|
||||
CHECK_FALSE(oxr_input_transform_process(
|
||||
transforms, num_transforms, &input, &output));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("From bool input") {
|
||||
input.type = XRT_INPUT_TYPE_BOOLEAN;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_bool", &transforms, &num_transforms));
|
||||
// Identity and a bool-to-float
|
||||
CHECK(num_transforms == 2);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
SECTION("False") {
|
||||
input.value.boolean = false;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(0.0f == output.value.vec1.x);
|
||||
}
|
||||
SECTION("True") {
|
||||
input.value.boolean = true;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(1.0f == output.value.vec1.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Bool action") {
|
||||
XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT;
|
||||
SECTION("From Bool identity") {
|
||||
input.type = XRT_INPUT_TYPE_BOOLEAN;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "bool_action",
|
||||
"/dummy_bool", &transforms, &num_transforms));
|
||||
CHECK(num_transforms == 1);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
SECTION("Roundtrip") {
|
||||
auto value = GENERATE(values({0, 1}));
|
||||
input.value.boolean = bool(value);
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(input.value.boolean == output.value.boolean);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("From Vec1 -1 to 1") {
|
||||
input.type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "bool_action",
|
||||
"/dummy_float", &transforms, &num_transforms));
|
||||
CHECK(num_transforms == 2);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
SECTION("True") {
|
||||
auto value = GENERATE(values({0.5f, 1.f}));
|
||||
input.value.vec1.x = value;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(output.value.boolean == true);
|
||||
}
|
||||
SECTION("False") {
|
||||
auto value = GENERATE(values({0.0f, -1.f}));
|
||||
input.value.vec1.x = value;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(output.value.boolean == false);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("From Vec1 0 to 1") {
|
||||
input.type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "bool_action",
|
||||
"/dummy_float", &transforms, &num_transforms));
|
||||
CHECK(num_transforms == 2);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
SECTION("True") {
|
||||
auto value = GENERATE(values({0.95f, 1.f}));
|
||||
input.value.vec1.x = value;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(output.value.boolean == true);
|
||||
}
|
||||
SECTION("False") {
|
||||
auto value = GENERATE(values({0.0f, 0.5f}));
|
||||
input.value.vec1.x = value;
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(output.value.boolean == false);
|
||||
}
|
||||
}
|
||||
SECTION("From Vec2") {
|
||||
input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
|
||||
input.value.vec2.x = -1;
|
||||
input.value.vec2.y = 1;
|
||||
SECTION("x") {
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_vec2/x", &transforms, &num_transforms));
|
||||
CHECK(num_transforms == 3);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(false == output.value.boolean);
|
||||
}
|
||||
SECTION("y") {
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy_vec2/y", &transforms, &num_transforms));
|
||||
CHECK(num_transforms == 3);
|
||||
CHECK(transforms != nullptr);
|
||||
|
||||
CHECK(oxr_input_transform_process(transforms, num_transforms,
|
||||
&input, &output));
|
||||
CHECK(true == output.value.boolean);
|
||||
}
|
||||
SECTION("no component") {
|
||||
CHECK_FALSE(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "float_action",
|
||||
"/dummy", &transforms, &num_transforms));
|
||||
// Shouldn't make a transform, not possible
|
||||
CHECK(num_transforms == 0);
|
||||
CHECK(transforms == nullptr);
|
||||
|
||||
// shouldn't do anything, but shouldn't explode.
|
||||
CHECK_FALSE(oxr_input_transform_process(
|
||||
transforms, num_transforms, &input, &output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Pose action") {
|
||||
XrActionType action_type = XR_ACTION_TYPE_POSE_INPUT;
|
||||
SECTION("From Pose identity") {
|
||||
input.type = XRT_INPUT_TYPE_POSE;
|
||||
CHECK(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "pose_action",
|
||||
"/dummy_pose", &transforms, &num_transforms));
|
||||
// Identity, just so this binding doesn't get culled.
|
||||
CHECK(num_transforms == 1);
|
||||
}
|
||||
SECTION("From other input") {
|
||||
auto input_type = GENERATE(values(
|
||||
{XRT_INPUT_TYPE_BOOLEAN, XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE,
|
||||
XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE,
|
||||
XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE,
|
||||
XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE}));
|
||||
CAPTURE(input_type);
|
||||
input.type = input_type;
|
||||
CHECK_FALSE(oxr_input_transform_create_chain(
|
||||
&log, &slog, input.type, action_type, "pose_action", "/dummy",
|
||||
&transforms, &num_transforms));
|
||||
// not possible
|
||||
CHECK(num_transforms == 0);
|
||||
CHECK(transforms == nullptr);
|
||||
}
|
||||
}
|
||||
oxr_log_slog(&log, &slog);
|
||||
oxr_input_transform_destroy(&transforms);
|
||||
CHECK(NULL == transforms);
|
||||
}
|
10
tests/tests_main.cpp
Normal file
10
tests/tests_main.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2020, Collabora, Ltd.
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
/*!
|
||||
* @file
|
||||
* @brief Translation unit to build Catch2 main.
|
||||
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch/catch.hpp"
|
Loading…
Reference in a new issue