diff --git a/src/xrt/state_trackers/oxr/oxr_api_action.c b/src/xrt/state_trackers/oxr/oxr_api_action.c index e96b5b13e..8502ea9ab 100644 --- a/src/xrt/state_trackers/oxr/oxr_api_action.c +++ b/src/xrt/state_trackers/oxr/oxr_api_action.c @@ -84,11 +84,16 @@ process_dpad(struct oxr_logger *log, for (size_t i = 0; i < ARRAY_SIZE(entry->dpads); i++) { // Have we found a empty slot, add it. if (entry->dpads[i].binding == XR_NULL_PATH) { - entry->dpads[i] = *dpad; - - // Don't leave potentially dangling pointers. - entry->dpads[i].next = NULL; - entry->dpads[i].actionSet = XR_NULL_HANDLE; + struct oxr_dpad_binding_modification dpad_binding = { + .binding = dpad->binding, + .settings = { + .forceThreshold = dpad->forceThreshold, + .forceThresholdReleased = dpad->forceThresholdReleased, + .centerRegion = dpad->centerRegion, + .wedgeAngle = dpad->wedgeAngle, + .isSticky = dpad->isSticky, + }}; + entry->dpads[i] = dpad_binding; entry->dpad_count++; added = true; diff --git a/src/xrt/state_trackers/oxr/oxr_dpad.c b/src/xrt/state_trackers/oxr/oxr_dpad.c index cdc0b7361..59e9e3cdd 100644 --- a/src/xrt/state_trackers/oxr/oxr_dpad.c +++ b/src/xrt/state_trackers/oxr/oxr_dpad.c @@ -63,7 +63,7 @@ oxr_dpad_state_get_or_add(struct oxr_dpad_state *state, uint64_t key) void oxr_dpad_state_deinit(struct oxr_dpad_state *state) { - if (state->uhi != NULL) { + if (state != NULL && state->uhi != NULL) { u_hashmap_int_clear_and_call_for_each(state->uhi, destroy_callback, NULL); u_hashmap_int_destroy(&state->uhi); } diff --git a/src/xrt/state_trackers/oxr/oxr_input.c b/src/xrt/state_trackers/oxr/oxr_input.c index ba9f0fad6..d0414d599 100644 --- a/src/xrt/state_trackers/oxr/oxr_input.c +++ b/src/xrt/state_trackers/oxr/oxr_input.c @@ -1258,6 +1258,115 @@ oxr_action_populate_input_transform(struct oxr_logger *log, return oxr_input_transform_create_chain(log, slog, t, act->data->action_type, act->data->name, str, &action_input->transforms, &action_input->transform_count); } +/*! + * Find dpad settings in @p dpad_entry whose binding path + * is a prefix of @p bound_path_string. + * + * @returns true if settings were found and written to @p out_dpad_settings + */ +static bool +find_matching_dpad(struct oxr_logger *log, + struct oxr_instance *inst, + struct oxr_dpad_entry *dpad_entry, + const char *bound_path_string, + struct oxr_dpad_binding_modification **out_dpad_binding) +{ + if (dpad_entry != NULL) { + for (uint32_t i = 0; i < dpad_entry->dpad_count; i++) { + const char *dpad_path_string; + size_t dpad_path_length; + oxr_path_get_string(log, inst, dpad_entry->dpads[i].binding, &dpad_path_string, + &dpad_path_length); + if (strncmp(bound_path_string, dpad_path_string, dpad_path_length) == 0) { + *out_dpad_binding = &dpad_entry->dpads[i]; + return true; + } + } + } + return false; +} + +/*! + * Try to produce a transform chain to create a dpad button from the selected input + * (potentially using other inputs like `/force` in the process) + * + * Populates @p action_input->transforms and @p action_input->transform_count on + * success. + * + * @returns false if it could not, true if it could + */ +static bool +oxr_action_populate_input_transform_dpad(struct oxr_logger *log, + struct oxr_sink_logger *slog, + struct oxr_session *sess, + struct oxr_action *act, + struct oxr_dpad_entry *dpad_entry, + enum oxr_dpad_region dpad_region, + struct oxr_interaction_profile *profile, + struct oxr_action_input *action_inputs, + uint32_t action_input_count, + uint32_t selected_input) +{ + struct oxr_action_input *action_input = &(action_inputs[selected_input]); + assert(action_input->transforms == NULL); + assert(action_input->transform_count == 0); + + const char *bound_path_string; + size_t bound_path_length; + oxr_path_get_string(log, sess->sys->inst, action_input->bound_path, &bound_path_string, &bound_path_length); + + // find correct dpad entry + struct oxr_dpad_binding_modification *dpad_binding_modification = NULL; + find_matching_dpad(log, sess->sys->inst, dpad_entry, bound_path_string, &dpad_binding_modification); + + enum xrt_input_type t = XRT_GET_INPUT_TYPE(action_input->input->name); + enum xrt_input_type activate_t = XRT_GET_INPUT_TYPE(action_input->dpad_activate_name); + + return oxr_input_transform_create_chain_dpad( + log, slog, t, act->data->action_type, bound_path_string, dpad_binding_modification, dpad_region, activate_t, + action_input->dpad_activate, &action_input->transforms, &action_input->transform_count); +} + +// based on get_subaction_path_from_path +static bool +get_dpad_region_from_path(struct oxr_logger *log, + struct oxr_instance *inst, + XrPath path, + enum oxr_dpad_region *out_dpad_region) +{ + const char *str = NULL; + size_t length = 0; + XrResult ret; + + ret = oxr_path_get_string(log, inst, path, &str, &length); + if (ret != XR_SUCCESS) { + return false; + } + + // TODO: surely there's a better way to do this? + if (length >= 10 && strncmp("/dpad_left", str + (length - 10), 10) == 0) { + *out_dpad_region = OXR_DPAD_REGION_LEFT; + return true; + } + if (length >= 11 && strncmp("/dpad_right", str + (length - 11), 11) == 0) { + *out_dpad_region = OXR_DPAD_REGION_RIGHT; + return true; + } + if (length >= 8 && strncmp("/dpad_up", str + (length - 8), 8) == 0) { + *out_dpad_region = OXR_DPAD_REGION_UP; + return true; + } + if (length >= 10 && strncmp("/dpad_down", str + (length - 10), 10) == 0) { + *out_dpad_region = OXR_DPAD_REGION_DOWN; + return true; + } + if (length >= 12 && strncmp("/dpad_center", str + (length - 12), 12) == 0) { + *out_dpad_region = OXR_DPAD_REGION_CENTER; + return true; + } + + return false; +} static void oxr_action_bind_io(struct oxr_logger *log, @@ -1282,9 +1391,18 @@ oxr_action_bind_io(struct oxr_logger *log, cache->current.active = true; cache->inputs = U_TYPED_ARRAY_CALLOC(struct oxr_action_input, input_count); for (uint32_t i = 0; i < input_count; i++) { - // Only add the input if we can find a transform. - if (oxr_action_populate_input_transform(log, slog, sess, act, &(inputs[i]))) { + + enum oxr_dpad_region dpad_region; + if (get_dpad_region_from_path(log, sess->sys->inst, inputs[i].bound_path, &dpad_region)) { + struct oxr_dpad_entry *entry = + oxr_dpad_state_get(&profile->dpad_state, act->act_set->act_set_key); + if (oxr_action_populate_input_transform_dpad(log, slog, sess, act, entry, dpad_region, + profile, inputs, input_count, i)) { + cache->inputs[count++] = inputs[i]; + continue; + } + } else if (oxr_action_populate_input_transform(log, slog, sess, act, &(inputs[i]))) { cache->inputs[count++] = inputs[i]; continue; } diff --git a/src/xrt/state_trackers/oxr/oxr_input_transform.c b/src/xrt/state_trackers/oxr/oxr_input_transform.c index f38d478bf..17f75acb4 100644 --- a/src/xrt/state_trackers/oxr/oxr_input_transform.c +++ b/src/xrt/state_trackers/oxr/oxr_input_transform.c @@ -8,6 +8,8 @@ * @ingroup oxr_input_transform */ +#include "math/m_mathinclude.h" + #include "oxr_input_transform.h" #include "oxr_logger.h" #include "oxr_objects.h" @@ -105,6 +107,30 @@ oxr_input_transform_init_vec2_get_y(struct oxr_input_transform *transform, const return true; } +bool +oxr_input_transform_init_vec2_dpad(struct oxr_input_transform *transform, + const struct oxr_input_transform *parent, + struct oxr_dpad_settings dpad_settings, + enum oxr_dpad_region dpad_region, + enum xrt_input_type activation_input_type, + struct xrt_input *activation_input) +{ + 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_DPAD; + transform->result_type = XRT_INPUT_TYPE_BOOLEAN; + transform->data.dpad_state.settings = dpad_settings; + transform->data.dpad_state.bound_region = dpad_region; + transform->data.dpad_state.activation_input_type = activation_input_type; + transform->data.dpad_state.activation_input = activation_input; + transform->data.dpad_state.already_active = activation_input == NULL; + + return true; +} + bool oxr_input_transform_init_threshold(struct oxr_input_transform *transform, const struct oxr_input_transform *parent, @@ -148,7 +174,7 @@ oxr_input_transform_init_bool_to_vec1(struct oxr_input_transform *transform, } bool -oxr_input_transform_process(const struct oxr_input_transform *transform, +oxr_input_transform_process(struct oxr_input_transform *transform, size_t transform_count, const struct oxr_input_value_tagged *input, struct oxr_input_value_tagged *out) @@ -158,7 +184,7 @@ oxr_input_transform_process(const struct oxr_input_transform *transform, } struct oxr_input_value_tagged data = *input; for (size_t i = 0; i < transform_count; ++i) { - const struct oxr_input_transform *xform = &(transform[i]); + struct oxr_input_transform *xform = &(transform[i]); switch (xform->type) { case INPUT_TRANSFORM_IDENTITY: // do nothing @@ -178,6 +204,75 @@ oxr_input_transform_process(const struct oxr_input_transform *transform, data.value.boolean ? xform->data.bool_to_vec1.true_val : xform->data.bool_to_vec1.false_val; break; } + case INPUT_TRANSFORM_DPAD: { + struct oxr_input_transform_dpad_data *dpad_state = &xform->data.dpad_state; + + if (dpad_state->activation_input != NULL) { + bool active = true; + + switch (dpad_state->activation_input_type) { + case XRT_INPUT_TYPE_BOOLEAN: { + active = dpad_state->activation_input->value.boolean; + break; + } + case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE: { + float force = dpad_state->activation_input->value.vec1.x; + active = (force >= dpad_state->settings.forceThreshold) || + (dpad_state->already_active && + force >= dpad_state->settings.forceThresholdReleased); + break; + } + default: active = false; + } + + dpad_state->already_active = active; + if (!active) { + dpad_state->active_regions = OXR_DPAD_REGION_CENTER; + data.value.boolean = false; + break; + } + } + + enum oxr_dpad_region bound_region = dpad_state->bound_region; + enum oxr_dpad_region active_regions = OXR_DPAD_REGION_CENTER; + + for (int i = 0; i < 4; i++) { + enum oxr_dpad_region query_region = 1 << i; + + bool rot90 = + (query_region == OXR_DPAD_REGION_LEFT) || (query_region == OXR_DPAD_REGION_RIGHT); + bool rot180 = + (query_region == OXR_DPAD_REGION_DOWN) || (query_region == OXR_DPAD_REGION_RIGHT); + + float localX = rot90 ? data.value.vec2.y : data.value.vec2.x; + float localY = rot90 ? -data.value.vec2.x : data.value.vec2.y; + if (rot180) { + localX = -localX; + localY = -localY; + } + + float centerRadius = dpad_state->settings.centerRegion; + if (localX * localX + localY * localY <= centerRadius * centerRadius) { + continue; + } + + float tanXY = atan2f(localX, localY); + float halfAngle = dpad_state->settings.wedgeAngle / 2.0f; + if (-halfAngle < tanXY && tanXY <= halfAngle) { + active_regions |= query_region; + } + } + + if (!dpad_state->already_active || !dpad_state->settings.isSticky || + (dpad_state->active_regions == OXR_DPAD_REGION_CENTER) || + (active_regions == OXR_DPAD_REGION_CENTER)) { + dpad_state->active_regions = active_regions; + } + + data.value.boolean = (dpad_state->active_regions == bound_region) || + ((dpad_state->active_regions & bound_region) != 0); + break; + } case INPUT_TRANSFORM_INVALID: default: return false; } @@ -346,3 +441,72 @@ oxr_input_transform_create_chain(struct oxr_logger *log, return true; } + +bool +oxr_input_transform_create_chain_dpad(struct oxr_logger *log, + struct oxr_sink_logger *slog, + enum xrt_input_type input_type, + XrActionType result_type, + const char *bound_path_string, + struct oxr_dpad_binding_modification *dpad_binding_modification, + enum oxr_dpad_region dpad_region, + enum xrt_input_type activation_input_type, + struct xrt_input *activation_input, + struct oxr_input_transform **out_transforms, + size_t *out_transform_count) +{ + struct oxr_input_transform chain[OXR_MAX_INPUT_TRANSFORMS] = {0}; + + // these default settings are specified by OpenXR and thus must not be changed + struct oxr_dpad_settings dpad_settings = { + .forceThreshold = 0.5f, + .forceThresholdReleased = 0.4f, + .centerRegion = 0.5f, + .wedgeAngle = M_PI_2, + .isSticky = false, + }; + + if (dpad_binding_modification != NULL) { + dpad_settings = dpad_binding_modification->settings; + } + + oxr_slog(slog, "\t\tAdding dpad transform from '%s' to '%s'\n", xr_action_type_to_str(result_type), + xrt_input_type_to_str(input_type)); + + struct oxr_input_transform *current_xform = &(chain[0]); + if (!oxr_input_transform_init_root(current_xform, input_type)) { + *out_transform_count = 0; + *out_transforms = NULL; + return false; + } + + // We start over here. + size_t transform_count = 0; + input_type = current_xform->result_type; + if (input_type != XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE) { + oxr_slog(slog, "\t\t\tUnexpected input type for dpad binding %s\n", bound_path_string); + return false; + } + if (result_type != XR_ACTION_TYPE_BOOLEAN_INPUT) { + oxr_slog(slog, "\t\t\tUnexpected output type for dpad binding %s\n", bound_path_string); + return false; + } + + struct oxr_input_transform *new_xform = &(chain[transform_count]); + if (!oxr_input_transform_init_vec2_dpad(new_xform, current_xform, dpad_settings, dpad_region, + activation_input_type, activation_input)) { + // Error has already been logged. + + *out_transform_count = 0; + *out_transforms = NULL; + return false; + } + + current_xform = new_xform; + transform_count++; + + *out_transform_count = transform_count; + *out_transforms = oxr_input_transform_clone_chain(chain, transform_count); + + return true; +} diff --git a/src/xrt/state_trackers/oxr/oxr_input_transform.h b/src/xrt/state_trackers/oxr/oxr_input_transform.h index 267c87099..f661d32c1 100644 --- a/src/xrt/state_trackers/oxr/oxr_input_transform.h +++ b/src/xrt/state_trackers/oxr/oxr_input_transform.h @@ -11,6 +11,7 @@ #pragma once #include "xrt/xrt_device.h" +#include "oxr_objects.h" // we need no platform-specific defines from OpenXR. #include "openxr/openxr.h" @@ -80,6 +81,15 @@ enum oxr_input_transform_type * @see oxr_input_transform_bool_to_vec1_data */ INPUT_TRANSFORM_BOOL_TO_VEC1, + + /*! + * Interpret a 2D joystick or trackpad as a dpad. + * + * This transform type has data: + * + * @see oxr_input_transform_dpad_data + */ + INPUT_TRANSFORM_DPAD, }; struct oxr_input_transform; @@ -110,6 +120,21 @@ struct oxr_input_transform_bool_to_vec1_data float false_val; }; +/*! + * Data required for INPUT_TRANSFORM_DPAD + * @see oxr_input_transform + * @see INPUT_TRANSFORM_DPAD + */ +struct oxr_input_transform_dpad_data +{ + enum oxr_dpad_region bound_region; + enum oxr_dpad_region active_regions; + struct oxr_dpad_settings settings; + enum xrt_input_type activation_input_type; + struct xrt_input *activation_input; + bool already_active; +}; + /*! * Variant type for input transforms. * @@ -139,6 +164,11 @@ struct oxr_input_transform * INPUT_TRANSFORM_BOOL_TO_VEC1 */ struct oxr_input_transform_bool_to_vec1_data bool_to_vec1; + /*! + * Populated when oxr_input_transform::type is + * INPUT_TRANSFORM_DPAD + */ + struct oxr_input_transform_dpad_data dpad_state; } data; }; @@ -173,7 +203,7 @@ oxr_input_transform_destroy(struct oxr_input_transform **transform_ptr); * @public @memberof oxr_input_transform */ bool -oxr_input_transform_process(const struct oxr_input_transform *transforms, +oxr_input_transform_process(struct oxr_input_transform *transforms, size_t transform_count, const struct oxr_input_value_tagged *input, struct oxr_input_value_tagged *out); @@ -300,6 +330,40 @@ oxr_input_transform_create_chain(struct oxr_logger *log, struct oxr_input_transform **out_transforms, size_t *out_transform_count); +/*! + * Create a transform array to process a 2D input plus activation input to a dpad. + * + * @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] bound_path_string The path name string that has been bound. + * @param[in] dpad_settings The dpad settings provided by the application + * as a binding modification. NULL means use the defaults as per the spec. + * @param[in] dpad_region The dpad region associated with this binding + * @param[in] activation_input_type The type of the activation input + * @param[in] activation_input The activation input, i.e. the input used + * to determine when the emulated dpad buttons should activate + * @param[out] out_transforms A pointer that will be populated with the output + * array's address, or NULL. + * @param[out] out_transform_count Where to populate the array size + * @return false if not possible + * + * @relates oxr_input_transform + */ +bool +oxr_input_transform_create_chain_dpad(struct oxr_logger *log, + struct oxr_sink_logger *slog, + enum xrt_input_type input_type, + XrActionType result_type, + const char *bound_path_string, + struct oxr_dpad_binding_modification *dpad_binding_modification, + enum oxr_dpad_region dpad_region, + enum xrt_input_type activation_input_type, + struct xrt_input *activation_input, + struct oxr_input_transform **out_transforms, + size_t *out_transform_count); + /*! * @} */ diff --git a/src/xrt/state_trackers/oxr/oxr_objects.h b/src/xrt/state_trackers/oxr/oxr_objects.h index 2e11122d8..c7002698e 100644 --- a/src/xrt/state_trackers/oxr/oxr_objects.h +++ b/src/xrt/state_trackers/oxr/oxr_objects.h @@ -173,6 +173,18 @@ enum oxr_subaction_path OXR_SUB_ACTION_PATH_GAMEPAD, }; +/*! + * Region of a dpad binding that an input is mapped to + */ +enum oxr_dpad_region +{ + OXR_DPAD_REGION_CENTER = 0, + OXR_DPAD_REGION_UP = (1 << 0), + OXR_DPAD_REGION_DOWN = (1 << 1), + OXR_DPAD_REGION_LEFT = (1 << 2), + OXR_DPAD_REGION_RIGHT = (1 << 3), +}; + /*! * Tracks the state of a image that belongs to a @ref oxr_swapchain. */ @@ -1596,6 +1608,29 @@ oxr_session_success_focused_result(struct oxr_session *session) } } +/*! + * dpad settings we need extracted from XrInteractionProfileDpadBindingEXT + * + * @ingroup oxr_input + */ +struct oxr_dpad_settings +{ + float forceThreshold; + float forceThresholdReleased; + float centerRegion; + float wedgeAngle; + bool isSticky; +}; + +/*! + * dpad binding extracted from XrInteractionProfileDpadBindingEXT + */ +struct oxr_dpad_binding_modification +{ + XrPath binding; + struct oxr_dpad_settings settings; +}; + /*! * A entry in the dpad state for one action set. * diff --git a/src/xrt/state_trackers/oxr/oxr_pretty_print.c b/src/xrt/state_trackers/oxr/oxr_pretty_print.c index 26379b7dc..ec782fd49 100644 --- a/src/xrt/state_trackers/oxr/oxr_pretty_print.c +++ b/src/xrt/state_trackers/oxr/oxr_pretty_print.c @@ -9,6 +9,7 @@ #include "oxr_objects.h" // For now needs to come before oxr_logger.h #include "oxr_logger.h" +#include "oxr_input_transform.h" #include "oxr_pretty_print.h" diff --git a/src/xrt/state_trackers/oxr/oxr_space.c b/src/xrt/state_trackers/oxr/oxr_space.c index 61aa2417b..328589677 100644 --- a/src/xrt/state_trackers/oxr/oxr_space.c +++ b/src/xrt/state_trackers/oxr/oxr_space.c @@ -20,6 +20,7 @@ #include "oxr_objects.h" #include "oxr_logger.h" #include "oxr_handle.h" +#include "oxr_input_transform.h" #include "oxr_chain.h" #include "oxr_pretty_print.h" diff --git a/tests/tests_input_transform.cpp b/tests/tests_input_transform.cpp index c302029f3..dbd533dd6 100644 --- a/tests/tests_input_transform.cpp +++ b/tests/tests_input_transform.cpp @@ -6,6 +6,8 @@ * @author Ryan Pavlik */ +#include "math/m_mathinclude.h" + #include "catch/catch.hpp" #include @@ -315,3 +317,236 @@ TEST_CASE("input_transform") oxr_input_transform_destroy(&transforms); CHECK(NULL == transforms); } + + +struct dpad_test_case +{ + float x; + float y; + enum oxr_dpad_region active_regions; +}; + + +TEST_CASE("input_transform_dpad") +{ + struct oxr_logger log; + oxr_log_init(&log, "test"); + struct oxr_sink_logger slog = {}; + + struct oxr_input_transform *transforms = NULL; + size_t transform_count = 0; + + oxr_input_value_tagged input = {}; + oxr_input_value_tagged output = {}; + + struct oxr_dpad_binding_modification *dpad_binding_modification = NULL; + enum xrt_input_type activation_input_type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE; + struct xrt_input activation_input = {}; + enum oxr_dpad_region dpad_region = OXR_DPAD_REGION_UP; + + SECTION("Default settings") + { + XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT; + + SECTION("without an activation input") + { + input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE; + + CHECK(oxr_input_transform_create_chain_dpad( + &log, &slog, input.type, action_type, "/dummy_vec2/dpad_up", dpad_binding_modification, + dpad_region, activation_input_type, NULL, &transforms, &transform_count)); + CHECK(transform_count == 1); + CHECK(transforms != nullptr); + CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD); + + + SECTION("up region is off in center") + { + input.value.vec2.x = 0.0f; + input.value.vec2.y = 0.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + + SECTION("up region is on when pointing up") + { + input.value.vec2.x = 0.0f; + input.value.vec2.y = 1.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + } + + struct dpad_test_case cases[9] = { + // obvious + {0.0f, 0.0f, OXR_DPAD_REGION_CENTER}, + {0.0f, 1.0f, OXR_DPAD_REGION_UP}, + {0.0f, -1.0f, OXR_DPAD_REGION_DOWN}, + {-1.0f, 0.0f, OXR_DPAD_REGION_LEFT}, + {1.0f, 0.0f, OXR_DPAD_REGION_RIGHT}, + // boundary cases + {1.0f, 1.0f, OXR_DPAD_REGION_UP}, + {-1.0f, -1.0f, OXR_DPAD_REGION_DOWN}, + {-1.0f, 1.0f, OXR_DPAD_REGION_LEFT}, + {1.0f, -1.0f, OXR_DPAD_REGION_RIGHT}, + }; + + for (uint32_t i = 0; i < ARRAY_SIZE(cases); i++) { + DYNAMIC_SECTION("with (x, y) of (" << cases[i].x << ", " << cases[i].y << ")") + { + input.value.vec2.x = cases[i].x; + input.value.vec2.y = cases[i].y; + CHECK( + oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(cases[i].active_regions == transforms[0].data.dpad_state.active_regions); + } + } + } + SECTION("with a boolean activation input") + { + input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE; + input.value.vec2.x = 0.0f; + input.value.vec2.y = 1.0f; + + activation_input_type = XRT_INPUT_TYPE_BOOLEAN; + + CHECK(oxr_input_transform_create_chain_dpad( + &log, &slog, input.type, action_type, "/dummy_vec2/dpad_up", dpad_binding_modification, + dpad_region, activation_input_type, &activation_input, &transforms, &transform_count)); + CHECK(transform_count == 1); + CHECK(transforms != nullptr); + CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD); + + SECTION("when activation input is set to true") + { + activation_input.value.boolean = true; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + } + SECTION("when activation input is set to false") + { + activation_input.value.boolean = false; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + } + SECTION("with a float activation input") + { + input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE; + input.value.vec2.x = 0.0f; + input.value.vec2.y = 1.0f; + + activation_input_type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE; + + CHECK(oxr_input_transform_create_chain_dpad( + &log, &slog, input.type, action_type, "/dummy_vec2/dpad_up", dpad_binding_modification, + dpad_region, activation_input_type, &activation_input, &transforms, &transform_count)); + CHECK(transform_count == 1); + CHECK(transforms != nullptr); + CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD); + + SECTION("when activation input is set to 1.0") + { + activation_input.value.vec1.x = 1.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + } + SECTION("when activation input is set to 0.0") + { + activation_input.value.vec1.x = 0.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + SECTION("when activation input varies") + { + activation_input.value.vec1.x = 0.45f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + activation_input.value.vec1.x = 0.6f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + activation_input.value.vec1.x = 0.45f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + activation_input.value.vec1.x = 0.35f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + } + } + SECTION("Sticky enabled") + { + XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT; + + struct oxr_dpad_binding_modification dpad_binding_modification_val = { + XR_NULL_PATH, // XrPath binding, unused at this stage + { + 0.5f, // float forceThreshold + 0.4f, // float forceThresholdReleased + 0.5f, // float centerRegion + M_PI_2, // float wedgeAngle + true, // bool isSticky + }}; + dpad_binding_modification = &dpad_binding_modification_val; + + SECTION("without an activation input") + { + input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE; + input.value.vec2.x = 0.0f; + input.value.vec2.y = 1.0f; + + CHECK(oxr_input_transform_create_chain_dpad( + &log, &slog, input.type, action_type, "/dummy_vec2/dpad_up", dpad_binding_modification, + dpad_region, activation_input_type, NULL, &transforms, &transform_count)); + CHECK(transform_count == 1); + CHECK(transforms != nullptr); + CHECK(transforms[0].type == INPUT_TRANSFORM_DPAD); + + SECTION("up region is off in center") + { + input.value.vec2.x = 0.0f; + input.value.vec2.y = 0.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + + SECTION("up region is on when pointing up") + { + input.value.vec2.x = 0.0f; + input.value.vec2.y = 1.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + } + SECTION("up region is off when pointing down") + { + input.value.vec2.x = 0.0f; + input.value.vec2.y = -1.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + + SECTION("up region stays on when stick moves clockwise to down") + { + input.value.vec2.x = 0.0f; + input.value.vec2.y = 1.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + input.value.vec2.x = 1.0f; + input.value.vec2.y = 0.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + input.value.vec2.x = 0.0f; + input.value.vec2.y = -1.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(true == output.value.boolean); + input.value.vec2.x = 0.0f; + input.value.vec2.y = 0.0f; + CHECK(oxr_input_transform_process(transforms, transform_count, &input, &output)); + CHECK(false == output.value.boolean); + } + } + } + + oxr_log_slog(&log, &slog); + oxr_input_transform_destroy(&transforms); + CHECK(NULL == transforms); +} \ No newline at end of file