// Controller input for questshock's immersive Quest build: one OpenXR // action set (aim pose + trigger click per hand, a menu-toggle button on // the left controller) plus ray/quad hit-testing. Each hand's ray is // tried against the keyboard overlay, then the menu overlay (see // xr_overlay.h, xr_session.c), then - only if neither actually claims it // this frame (landing on it now, or continuing a touch/drag begun on a // previous frame - not merely because that overlay happens to be visible) // - the game quad xr_session.c submits, forwarding trigger edges to // whichever overlay claims the ray as synthetic touches. The game quad // itself acts as a plain desktop-style mouse pointer into the engine's // own UI (see xr_mouse.h) - absolute cursor position while the ray hits // it, plus a left click on the trigger edge; a pending click there // likewise keeps claiming the ray ahead of the keyboard/menu until // released, so the eventual mouse-up isn't lost if the ray strays. Since // none of the three targets' own feedback (the overlays' on-quad cursor, // drawn by their Java views; the engine's own mouse cursor sprite for the // game quad) gives any indication while the ray is short of actually // landing on something, every claimed target also builds a thin, // billboarded laser-beam quad per hand from the controller to wherever // the ray currently crosses that target's plane (see // xr_input_get_beam_layer()) - so aiming looks and behaves the same // whether the target is an overlay or the game quad itself. #ifndef QUESTSHOCK_XR_INPUT_H #define QUESTSHOCK_XR_INPUT_H #include #include #define XR_USE_PLATFORM_ANDROID 1 #define XR_USE_GRAPHICS_API_OPENGL_ES 1 #include #ifdef __cplusplus extern "C" { #endif // Call once, right after the session is created (see xr_session.c's // xr_create_instance_and_session()) - creates the action set/actions, // suggests Touch controller bindings, creates the per-hand aim action // spaces and a view-space reference (for the laser-beam billboard math), // creates the two per-hand beam swapchains (swapchain_format - the same // format shared by the game swapchain and both overlays), and attaches // the action set to the session. Returns false (logged, non-fatal - the // caller keeps rendering without input) if any of that fails. bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_format); // Call once per frame from xr_frame_end(), before releasing the acquired // swapchain image - syncs this frame's action states (always, so edge // detection stays correct even on frames with nothing to draw), handles // the menu_toggle button's edge, and hit-tests/dispatches each hand's ray // against keyboard/menu/game quad in that priority order (see the file // comment above). draw gates only the laser-beam visuals (and the // head-pose locate that feeds them) - if false (nothing to draw into this // frame, e.g. no swapchain image was acquired), hit-testing and // touch-forwarding still run. baseSpace/time must match whatever // xr_frame_begin() used to predict this frame. void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw); // Call once per hand (hand: 0=left, 1=right) after xr_input_sync_and_draw() // in the same frame - if that hand's ray claimed the keyboard, menu, or // game quad this frame (see the file comment above), fills *out_quad's // subImage/pose/size for its laser-beam ribbon and returns true. // space/eyeVisibility/layerFlags are left for the caller to set, same // convention as xr_overlay_render_and_build_layer(). Returns false // (out_quad untouched) if no beam should be shown for that hand this // frame. bool xr_input_get_beam_layer(int hand, XrCompositionLayerQuad *out_quad); // Recenters the player so the game quad ("virtual screen") appears directly // ahead of wherever they are standing and facing, from their current real head // pose. Never reads or writes questshock.config.json. Called automatically // once, from xr_input_sync_and_draw(), on the first frame with a valid head // pose after startup, unless a complete saved player_position was loaded (see // xr_input_skip_startup_recenter()): a saved position is relative to a // previous session's real-space origin, which is not stable across launches. // Returns false (no-op) if no valid head pose is available yet. bool xr_input_recenter_player_to_screen(void); // The Main Menu tab's "Reset Position" button (via qs_config.c). Like // xr_input_recenter_player_to_screen(), but re-anchors only the player's // position to the centre in front of the game quad; the current facing (yaw) // is kept, so the screen does not rotate to follow the gaze. Returns false // (no-op) if no valid head pose is available yet. bool xr_input_reset_player_position(void); // Called once, from qs_config_load_startup() (qs_config.c), when a // complete saved player_position was actually loaded from // questshock.config.json - cancels the pending automatic startup recenter // above so it doesn't immediately override the value that was just // applied. Safe to call regardless of whether xr_input_init() has even run // yet (this only touches the plain file-local flag, no XR calls). void xr_input_skip_startup_recenter(void); // =========================================================================== // Remappable controls ("player_controls" config group; see qs_config.c and // CONFIG.md). menu_toggle is not among them and has no getter/setter: it is // fixed (see xr_input_sync_and_draw()). // =========================================================================== // Number of remappable actions and each one's index<->name mapping, which // qs_config.c uses to parse/write the player_controls group without its own // copy of the list. The names are the keys used in questshock.config.json; // index_from_name() returns false for an unrecognized name. int xr_input_action_count(void); const char *xr_input_action_name(int action_index); bool xr_input_action_index_from_name(const char *name, int *out_index); // Current binding for one action, as an opaque int and as its string form // (e.g. "right_b", "trigger", "none" for unbound), the same strings used in // questshock.config.json and reported by capture. int xr_input_get_action_binding(int action_index); const char *xr_input_get_action_binding_name(int action_index); // Sets one action's binding from a config-file string. Returns false (binding // unchanged) for an unrecognized name, or for the symmetric "trigger"/ // "squeeze" on an action other than mouse_click/mouse_double_click. Does not // enforce one-action-per-input the way capture does: a hand-edited file that // binds two actions to one input is accepted as-is. bool xr_input_set_action_binding(int action_index, const char *raw_name); // Restores every action to its built-in default (the table xr_input_init() // starts from). qs_config.c calls this first for "Reset to Defaults", then // applies questshock.config.default.json's player_controls group on top if it // has one. void xr_input_reset_player_controls(void); // Begins/cancels rebind capture for one action (an index into // xr_input_action_name()'s list): the next fresh button or thumbstick-direction // edge on any hand becomes that action's binding and is reported via // qs_config_notify_control_capture_result(); with no press, capture cancels // after 8 seconds. Called via qs_config.c's cross-thread request functions. // The menu button is never captured. void xr_input_capture_begin(int action_index); void xr_input_capture_cancel(void); // The OpenXR interaction profile currently bound (e.g. // "/interaction_profiles/oculus/touch_controller"), refreshed once per frame. // MenuOverlay.java's Controller Options tab uses it to pick a file from // controller_profiles/ (see CONFIG.md). Empty until the first successful // query. const char *xr_input_get_active_interaction_profile_path(void); // Set by MenuOverlay.java when the Controller Options tab becomes or stops // being the menu's current tab. While it is open, the menu's thumbstick // locomotion (snap turn/smooth move) is disabled. void xr_input_set_controller_options_tab_open(bool open); void xr_input_shutdown(void); #ifdef __cplusplus } #endif #endif