Add VR locomotion: move/turn with thumbsticks while the menu is open
build / build (push) Successful in 2m25s
build / build (push) Successful in 2m25s
- Left thumbstick snap-turns, right thumbstick smooth-moves, letting the
player line themselves up with the fixed game quad ("virtual screen")
without needing physical room-scale space to do it in. Implemented via
a second OpenXR reference space (a "play space") derived from the real
LOCAL space by the inverse of the player's transform, so every existing
quad/hit-testing/rendering call site just needed g_local_space swapped
for it - no manual coordinate-transform math required elsewhere
- Generalized the ray/quad hit-testing in xr_input.c to a proper
oriented-plane intersection (needed once quads can rotate); verified it
degenerates to the old axis-aligned formula when unrotated, so the
keyboard and non-recentered menu are unaffected
- The menu launcher now re-centers in front of the player, correctly
oriented to face them, each time it's freshly reopened
- "Reset Position" (Main Menu tab) recenters using the player's current
real head pose - not a plain reset-to-origin, which only undid
thumbstick movement and ignored physical drift since the session
started. The same recenter runs once automatically at startup too, as a
fallback for when there's no complete saved position to use instead
- Position/orientation persists in questshock.config.json's new
player_position group (independent of savegames), including new
numeric-value support in the config file's hand-rolled JSON parser
(previously string-enums only)
This commit is contained in:
@@ -20,6 +20,12 @@
|
||||
#define LEFT 0
|
||||
#define RIGHT 1
|
||||
|
||||
// Same constant (deliberately not <math.h>'s M_PI - see xr_overlay.c's own
|
||||
// copy of this comment) as xr_session.c's play-space rebuild and
|
||||
// xr_overlay.c's quad-rotation math - this file's ray/oriented-quad
|
||||
// intersection and thumbstick-turn math need the same conversion.
|
||||
#define QS_DEG_TO_RAD (3.14159265358979323846f / 180.0f)
|
||||
|
||||
static const char *kHandName[2] = {"left", "right"};
|
||||
static const float kHandColor[2][4] = {
|
||||
{0.2f, 1.0f, 1.0f, 1.0f}, // left: cyan
|
||||
@@ -63,6 +69,10 @@ static XrAction g_space_key_action = XR_NULL_HANDLE;
|
||||
// SDL_TEXTINPUT needed (see the space_key injection below for why that
|
||||
// matters).
|
||||
static XrAction g_forward_key_action = XR_NULL_HANDLE;
|
||||
// Player locomotion while the menu is open (see xr_session.h's
|
||||
// xr_session_set_player_transform()) - left stick snap-turns, right stick
|
||||
// smooth-moves, both read from the same Vector2f action via subactionPath.
|
||||
static XrAction g_thumbstick_action = XR_NULL_HANDLE;
|
||||
static XrPath g_hand_path[2];
|
||||
static XrSpace g_aim_space[2] = {XR_NULL_HANDLE, XR_NULL_HANDLE};
|
||||
|
||||
@@ -75,6 +85,25 @@ static bool g_prev_select[2] = {false, false};
|
||||
static bool g_prev_menu = false;
|
||||
static bool g_prev_space_key = false;
|
||||
static bool g_prev_forward_key = false;
|
||||
// Snap-turn hysteresis - true when the left stick has returned near center
|
||||
// since the last turn it triggered, i.e. it's "armed" to trigger another
|
||||
// one; false from the moment a turn triggers until the stick re-centers,
|
||||
// so holding the stick deflected turns exactly once, not once per frame.
|
||||
static bool g_turn_ready = true;
|
||||
// Self-contained delta-time tracker for smooth movement - deliberately not
|
||||
// shared with xr_session.c's own SDL_GetTicks()-based FPS smoothing, which
|
||||
// is a private implementation detail of that file's stats display.
|
||||
static Uint32 g_last_move_ticks = 0;
|
||||
// True until either xr_input_recenter_player_to_screen() has succeeded
|
||||
// once this session, or xr_input_skip_startup_recenter() has been called -
|
||||
// checked once per frame in xr_input_sync_and_draw() (see its own comment
|
||||
// there) as a *fallback* default player position/orientation for when
|
||||
// there's no complete saved one to fall back on instead (see
|
||||
// qs_config_load_startup()'s own comment on when it calls
|
||||
// xr_input_skip_startup_recenter()). While still pending, retries every
|
||||
// frame until a valid head pose is actually available - typically the
|
||||
// very first frame, but tracking isn't guaranteed to be valid that early.
|
||||
static bool g_pending_startup_recenter = true;
|
||||
static bool g_game_prev_hit[2] = {false, false};
|
||||
static bool g_game_touch_active[2] = {false, false};
|
||||
static bool g_menu_prev_hit[2] = {false, false};
|
||||
@@ -282,6 +311,17 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
||||
"xrCreateAction(forward_key)"))
|
||||
return false;
|
||||
|
||||
// Player locomotion, see g_thumbstick_action's own declaration comment.
|
||||
XrActionCreateInfo thumbstickInfo = {XR_TYPE_ACTION_CREATE_INFO};
|
||||
strncpy(thumbstickInfo.actionName, "thumbstick", XR_MAX_ACTION_NAME_SIZE - 1);
|
||||
strncpy(thumbstickInfo.localizedActionName, "Thumbstick", XR_MAX_LOCALIZED_ACTION_NAME_SIZE - 1);
|
||||
thumbstickInfo.actionType = XR_ACTION_TYPE_VECTOR2F_INPUT;
|
||||
thumbstickInfo.countSubactionPaths = 2;
|
||||
thumbstickInfo.subactionPaths = g_hand_path;
|
||||
if (!xr_check(xrCreateAction(g_action_set, &thumbstickInfo, &g_thumbstick_action),
|
||||
"xrCreateAction(thumbstick)"))
|
||||
return false;
|
||||
|
||||
XrPath profilePath;
|
||||
xrStringToPath(instance, "/interaction_profiles/oculus/touch_controller", &profilePath);
|
||||
|
||||
@@ -294,7 +334,7 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
||||
// have at all (like a nonexistent .../trigger/click) fails the entire
|
||||
// xrSuggestInteractionProfileBindings call, not just that one entry.
|
||||
XrPath aimPoseLeftPath, aimPoseRightPath, triggerLeftPath, triggerRightPath, menuClickPath,
|
||||
aClickPath, bClickPath;
|
||||
aClickPath, bClickPath, thumbstickLeftPath, thumbstickRightPath;
|
||||
xrStringToPath(instance, "/user/hand/left/input/aim/pose", &aimPoseLeftPath);
|
||||
xrStringToPath(instance, "/user/hand/right/input/aim/pose", &aimPoseRightPath);
|
||||
xrStringToPath(instance, "/user/hand/left/input/trigger/value", &triggerLeftPath);
|
||||
@@ -302,6 +342,8 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
||||
xrStringToPath(instance, "/user/hand/left/input/menu/click", &menuClickPath);
|
||||
xrStringToPath(instance, "/user/hand/right/input/a/click", &aClickPath);
|
||||
xrStringToPath(instance, "/user/hand/right/input/b/click", &bClickPath);
|
||||
xrStringToPath(instance, "/user/hand/left/input/thumbstick", &thumbstickLeftPath);
|
||||
xrStringToPath(instance, "/user/hand/right/input/thumbstick", &thumbstickRightPath);
|
||||
|
||||
XrActionSuggestedBinding bindings[] = {
|
||||
{g_aim_pose_action, aimPoseLeftPath}, {g_aim_pose_action, aimPoseRightPath},
|
||||
@@ -309,6 +351,7 @@ bool xr_input_init(XrInstance instance, XrSession session, int64_t swapchain_for
|
||||
{g_menu_toggle_action, menuClickPath},
|
||||
{g_space_key_action, aClickPath},
|
||||
{g_forward_key_action, bClickPath},
|
||||
{g_thumbstick_action, thumbstickLeftPath}, {g_thumbstick_action, thumbstickRightPath},
|
||||
};
|
||||
XrInteractionProfileSuggestedBinding suggested = {XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
|
||||
suggested.interactionProfile = profilePath;
|
||||
@@ -387,28 +430,51 @@ static bool xr_input_try_overlay(XrOverlay *overlay, float titleBarVFraction,
|
||||
bool hadTouch = *touchActive;
|
||||
bool hadDrag = dragState->dragging && dragState->hand == hand;
|
||||
|
||||
float quadCenterX, quadCenterY, distance, halfWidth, halfHeight;
|
||||
float quadCenterX, quadCenterY, distance, halfWidth, halfHeight, yawDeg;
|
||||
xr_overlay_get_quad_extent(overlay, &quadCenterX, &quadCenterY, &distance, &halfWidth,
|
||||
&halfHeight);
|
||||
&halfHeight, &yawDeg);
|
||||
|
||||
// The quad's plane is z = -distance (facing the viewer along -Z) - a
|
||||
// ray parallel to it (fz ~ 0) never crosses. worldX/Y is the raw
|
||||
// world-space intersection point, needed as-is for dragging (which can
|
||||
// legitimately move the quad to where the ray isn't currently hitting
|
||||
// it); cx/cy are that same point in the quad's own [-1,1] local space;
|
||||
// hit/u/v (the equivalent 0..1, top-left-origin coordinates the Java
|
||||
// view's pixel grid and the on-quad hit logs use) are only meaningful
|
||||
// within the quad's current bounds, unlike planeHit.
|
||||
// General ray/oriented-quad intersection - the quad's plane passes
|
||||
// through (quadCenterX, quadCenterY, -distance) with normal
|
||||
// R_y(-yawDeg)*(0,0,1) (see xr_overlay.c's own pose-building comment on
|
||||
// the same rotation). Solved by rotating the ray's origin (relative to
|
||||
// the quad's center) and direction into the quad's own local frame via
|
||||
// R_y(+yawDeg) - the inverse of the render-time rotation - and
|
||||
// intersecting against the local z=0 plane there; A/B below are that
|
||||
// rotated frame's z-components of the (center-relative) origin and the
|
||||
// direction, so `t = -A/B` solves local_z(t) = A + t*B = 0. Degenerates
|
||||
// to exactly the old plain "z = -distance, no rotation" formula when
|
||||
// yawDeg is 0 (cosPhi=1, sinPhi=0) - true for every overlay except the
|
||||
// menu launcher right after xr_input_sync_and_draw() has re-centered it
|
||||
// in front of a turned player (see its own comment there).
|
||||
float phi = yawDeg * QS_DEG_TO_RAD;
|
||||
float cosPhi = cosf(phi), sinPhi = sinf(phi);
|
||||
float dx0 = location->pose.position.x - quadCenterX;
|
||||
float dz0 = location->pose.position.z + distance;
|
||||
float a = -dx0 * sinPhi + dz0 * cosPhi;
|
||||
float b = -fx * sinPhi + fz * cosPhi;
|
||||
|
||||
// worldX/Y is the raw base-space intersection point, needed as-is for
|
||||
// dragging (which can legitimately move the quad to where the ray
|
||||
// isn't currently hitting it, and always operates in base-space X/Y via
|
||||
// xr_overlay_set_position() regardless of any rotation); cx/cy are that
|
||||
// same point in the quad's own [-1,1] local space; hit/u/v (the
|
||||
// equivalent 0..1, top-left-origin coordinates the Java view's pixel
|
||||
// grid and the on-quad hit logs use) are only meaningful within the
|
||||
// quad's current bounds, unlike planeHit.
|
||||
bool planeHit = false, hit = false;
|
||||
float u = 0.0f, v = 0.0f, worldX = 0.0f, worldY = 0.0f, t = 0.0f;
|
||||
if (fabsf(fz) > 1e-5f) {
|
||||
t = (-distance - location->pose.position.z) / fz;
|
||||
if (fabsf(b) > 1e-5f) {
|
||||
t = -a / b;
|
||||
if (t > 0.0f) {
|
||||
planeHit = true;
|
||||
worldX = location->pose.position.x + t * fx;
|
||||
worldY = location->pose.position.y + t * fy;
|
||||
float cx = (worldX - quadCenterX) / halfWidth;
|
||||
float cy = (worldY - quadCenterY) / halfHeight;
|
||||
float worldZ = location->pose.position.z + t * fz;
|
||||
float localX = (worldX - quadCenterX) * cosPhi + (worldZ + distance) * sinPhi;
|
||||
float localY = worldY - quadCenterY;
|
||||
float cx = localX / halfWidth;
|
||||
float cy = localY / halfHeight;
|
||||
hit = fabsf(cx) <= 1.0f && fabsf(cy) <= 1.0f;
|
||||
u = (cx + 1.0f) * 0.5f;
|
||||
v = (1.0f - cy) * 0.5f;
|
||||
@@ -619,6 +685,126 @@ static bool xr_input_try_game_quad(int hand, const XrSpaceLocation *location, fl
|
||||
return hit;
|
||||
}
|
||||
|
||||
// Re-centers the menu launcher in front of wherever the player is
|
||||
// currently looking, each time it's freshly opened (see the menu-toggle
|
||||
// handling below) - otherwise a player who's walked/turned away from the
|
||||
// menu's last position (via the thumbstick locomotion further down, or a
|
||||
// manual drag) would find it stuck somewhere behind/beside them. Uses
|
||||
// xr_overlay_get_default_distance_m() (the overlay's original, never-
|
||||
// mutated distance_m) rather than duplicating MENU_DISTANCE_METERS here -
|
||||
// deliberately NOT the live distance_m from xr_overlay_get_quad_extent(),
|
||||
// which xr_overlay_set_pose() repurposes as a raw base-space Z coordinate
|
||||
// once rotation is involved (see that function's own comment) - reusing
|
||||
// that as a "how far in front" magnitude would drift the menu further
|
||||
// away with every reopen instead of holding a stable distance. Height
|
||||
// (center_y_m) doesn't have this problem (it's never touched by any
|
||||
// rotation math), so that one *is* read live, preserving a manual drag.
|
||||
// Independent of needBeams/draw below (a menu toggle can happen on a frame
|
||||
// nothing renders), so this does its own dedicated head locate rather than
|
||||
// reusing headLoc.
|
||||
static void xr_input_recenter_menu(XrOverlay *menuOverlay, XrSpace baseSpace, XrTime time) {
|
||||
XrSpaceLocation headLoc = {XR_TYPE_SPACE_LOCATION};
|
||||
const XrSpaceLocationFlags needed =
|
||||
XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT;
|
||||
if (!xr_check(xrLocateSpace(g_view_space, baseSpace, time, &headLoc),
|
||||
"xrLocateSpace(view, menu recenter)") ||
|
||||
(headLoc.locationFlags & needed) != needed)
|
||||
return;
|
||||
|
||||
float fx, fy, fz;
|
||||
quat_rotate_vec(&headLoc.pose.orientation, 0.0f, 0.0f, -1.0f, &fx, &fy, &fz);
|
||||
// Horizontal-only, matching the play space's own yaw-only rotation -
|
||||
// looking sharply up/down shouldn't tip the menu with you.
|
||||
float len = sqrtf(fx * fx + fz * fz);
|
||||
if (len < 1e-4f)
|
||||
return; // looking almost straight up/down - leave the existing pose alone
|
||||
fx /= len;
|
||||
fz /= len;
|
||||
|
||||
float currentX, currentY, liveDistance, halfWidth, halfHeight, currentYaw;
|
||||
xr_overlay_get_quad_extent(menuOverlay, ¤tX, ¤tY, &liveDistance, &halfWidth,
|
||||
&halfHeight, ¤tYaw);
|
||||
(void)currentX;
|
||||
(void)liveDistance;
|
||||
(void)halfWidth;
|
||||
(void)halfHeight;
|
||||
(void)currentYaw;
|
||||
float distance = xr_overlay_get_default_distance_m(menuOverlay);
|
||||
|
||||
float newX = headLoc.pose.position.x + fx * distance;
|
||||
float newZ = headLoc.pose.position.z + fz * distance;
|
||||
// xr_overlay_set_pose()'s distance_m is simply the quad's base-space Z
|
||||
// position negated (see xr_overlay_render_and_build_layer()) - not
|
||||
// necessarily positive once the quad can end up anywhere the player
|
||||
// has walked to, unlike every other overlay's fixed, always-in-front-
|
||||
// of-the-origin spawn pose.
|
||||
float newDistance = -newZ;
|
||||
// See xr_overlay_render_and_build_layer()'s own rotation comment -
|
||||
// solved so the quad's local front normal (0,0,1) ends up pointing
|
||||
// along -forward, i.e. back at the player looking along +forward at it.
|
||||
float newYawDeg = -atan2f(-fx, -fz) / QS_DEG_TO_RAD;
|
||||
|
||||
xr_overlay_set_pose(menuOverlay, newX, currentY, newDistance, newYawDeg);
|
||||
}
|
||||
|
||||
// See xr_input.h's own comment. Chooses a new player transform such that
|
||||
// the player's CURRENT real head pose maps to play-space position (0,0)
|
||||
// facing (0,0,-1) - i.e. exactly reproducing this app's own startup
|
||||
// framing (the fixed game quad dead ahead at its authored distance), just
|
||||
// re-anchored to wherever the player is standing/facing right now instead
|
||||
// of wherever their headset happened to be when the session first began.
|
||||
// Deliberately locates the head against the REAL local space (via
|
||||
// xr_session_get_local_space()), not the play space every other consumer
|
||||
// in this file uses - computing a brand new transform from the player's
|
||||
// real pose would be circular if measured through the very transform
|
||||
// being replaced.
|
||||
//
|
||||
// Math: with V(p) = R_y(yaw_deg) * p + (x, z) defined as "real-space pose p
|
||||
// maps to this play-space pose" (see xr_session_rebuild_play_space()'s own
|
||||
// derivation, which builds the inverse of this same V), solving
|
||||
// V(headPos) = (0,0) and V(headForward) = (0,0,-1) for (x, z, yaw_deg)
|
||||
// gives the formulas below - verified by substitution that a
|
||||
// player standing at the real origin facing the real default -Z (i.e. one
|
||||
// who has never moved) yields (0, 0, 0), the same as this feature's
|
||||
// original naive reset-to-identity behavior.
|
||||
bool xr_input_recenter_player_to_screen(void) {
|
||||
XrSpace localSpace = xr_session_get_local_space();
|
||||
if (localSpace == XR_NULL_HANDLE || g_view_space == XR_NULL_HANDLE)
|
||||
return false;
|
||||
|
||||
XrTime time = xr_session_get_predicted_display_time();
|
||||
XrSpaceLocation headLoc = {XR_TYPE_SPACE_LOCATION};
|
||||
const XrSpaceLocationFlags needed =
|
||||
XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT;
|
||||
if (!xr_check(xrLocateSpace(g_view_space, localSpace, time, &headLoc),
|
||||
"xrLocateSpace(view, recenter to screen)") ||
|
||||
(headLoc.locationFlags & needed) != needed)
|
||||
return false;
|
||||
|
||||
float fx, fy, fz;
|
||||
quat_rotate_vec(&headLoc.pose.orientation, 0.0f, 0.0f, -1.0f, &fx, &fy, &fz);
|
||||
float len = sqrtf(fx * fx + fz * fz);
|
||||
if (len < 1e-4f)
|
||||
return false; // looking almost straight up/down - nothing sane to recenter onto
|
||||
fx /= len;
|
||||
fz /= len;
|
||||
|
||||
// angle(0,-1) - angle(fx,fz), using the same atan2f(x, z) "angle"
|
||||
// convention as xr_overlay_render_and_build_layer()'s own rotation
|
||||
// comment (not the more usual atan2f(y, x)).
|
||||
float theta = atan2f(0.0f, -1.0f) - atan2f(fx, fz);
|
||||
float cosTheta = cosf(theta), sinTheta = sinf(theta);
|
||||
float hx = headLoc.pose.position.x, hz = headLoc.pose.position.z;
|
||||
float newX = -(hx * cosTheta + hz * sinTheta);
|
||||
float newZ = hx * sinTheta - hz * cosTheta;
|
||||
float newYawDeg = theta / QS_DEG_TO_RAD;
|
||||
|
||||
xr_session_set_player_transform(newX, newZ, newYawDeg);
|
||||
return true;
|
||||
}
|
||||
|
||||
void xr_input_skip_startup_recenter(void) { g_pending_startup_recenter = false; }
|
||||
|
||||
void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
if (g_action_set == XR_NULL_HANDLE)
|
||||
return;
|
||||
@@ -629,6 +815,12 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
syncInfo.activeActionSets = &activeSet;
|
||||
xr_check(xrSyncActions(g_session, &syncInfo), "xrSyncActions");
|
||||
|
||||
// See g_pending_startup_recenter's own comment - retried every frame
|
||||
// (cheap: one xrLocateSpace call) until head tracking actually has a
|
||||
// valid pose to recenter onto, typically the very first frame.
|
||||
if (g_pending_startup_recenter && xr_input_recenter_player_to_screen())
|
||||
g_pending_startup_recenter = false;
|
||||
|
||||
XrOverlay *menuOverlay = xr_session_get_menu_overlay();
|
||||
XrOverlay *keyboardOverlay = xr_session_get_keyboard_overlay();
|
||||
bool menuVisible = xr_overlay_is_visible(menuOverlay);
|
||||
@@ -702,14 +894,49 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
// fully independent (its own Close button is the only way
|
||||
// to hide it, see KeyboardOverlay.java).
|
||||
if (menuOverlay != NULL) {
|
||||
bool wasHidden = !menuVisible;
|
||||
xr_overlay_toggle_visible(menuOverlay);
|
||||
g_menu_prev_hit[LEFT] = g_menu_prev_hit[RIGHT] = false;
|
||||
g_menu_touch_active[LEFT] = g_menu_touch_active[RIGHT] = false;
|
||||
if (wasHidden && xr_overlay_is_visible(menuOverlay))
|
||||
xr_input_recenter_menu(menuOverlay, baseSpace, time);
|
||||
}
|
||||
} else if (!menuDown && g_prev_menu) {
|
||||
LOGI("XR: menu-toggle button UP");
|
||||
}
|
||||
g_prev_menu = menuDown;
|
||||
|
||||
// Snap turn - only while the menu is open (per the feature's
|
||||
// own design: repositioning is for lining yourself up with the
|
||||
// game quad from the menu, not a general gameplay locomotion
|
||||
// scheme). g_turn_ready is a hysteresis latch (see its own
|
||||
// declaration comment) so holding the stick past the threshold
|
||||
// turns exactly once, not once per frame - it only re-arms once
|
||||
// the stick has come back near center.
|
||||
if (menuVisible) {
|
||||
XrActionStateGetInfo turnInfo = {XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
turnInfo.action = g_thumbstick_action;
|
||||
turnInfo.subactionPath = g_hand_path[LEFT];
|
||||
XrActionStateVector2f turnState = {XR_TYPE_ACTION_STATE_VECTOR2F};
|
||||
xrGetActionStateVector2f(g_session, &turnInfo, &turnState);
|
||||
if (turnState.isActive) {
|
||||
const float kSnapThreshold = 0.7f;
|
||||
const float kSnapRearm = 0.3f;
|
||||
const float kSnapDegrees = 30.0f;
|
||||
float x = turnState.currentState.x;
|
||||
if (g_turn_ready && fabsf(x) > kSnapThreshold) {
|
||||
float px, pz, pyaw;
|
||||
xr_session_get_player_transform(&px, &pz, &pyaw);
|
||||
// Confirmed backwards on-device from the initial
|
||||
// (unverifiable-without-a-headset) guess - flipped.
|
||||
pyaw += (x > 0.0f) ? -kSnapDegrees : kSnapDegrees;
|
||||
xr_session_set_player_transform(px, pz, pyaw);
|
||||
g_turn_ready = false;
|
||||
} else if (fabsf(x) < kSnapRearm) {
|
||||
g_turn_ready = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hand == RIGHT) {
|
||||
@@ -786,6 +1013,63 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
SDL_PushEvent(&event);
|
||||
g_prev_forward_key = forwardKeyDown;
|
||||
}
|
||||
|
||||
// Smooth move - only while the menu is open, same reasoning as
|
||||
// the left stick's snap turn above. g_last_move_ticks is
|
||||
// refreshed every frame regardless of menuVisible (not just
|
||||
// while actively moving) so dt is always "time since the
|
||||
// previous frame" and never goes stale/huge across a stretch
|
||||
// with the menu closed.
|
||||
Uint32 nowTicks = SDL_GetTicks();
|
||||
Uint32 lastMoveTicks = g_last_move_ticks;
|
||||
g_last_move_ticks = nowTicks;
|
||||
if (menuVisible && lastMoveTicks != 0) {
|
||||
XrActionStateGetInfo moveInfo = {XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
moveInfo.action = g_thumbstick_action;
|
||||
moveInfo.subactionPath = g_hand_path[RIGHT];
|
||||
XrActionStateVector2f moveState = {XR_TYPE_ACTION_STATE_VECTOR2F};
|
||||
xrGetActionStateVector2f(g_session, &moveInfo, &moveState);
|
||||
const float kMoveDeadzone = 0.15f;
|
||||
float mx = moveState.currentState.x, my = moveState.currentState.y;
|
||||
float mag = sqrtf(mx * mx + my * my);
|
||||
if (moveState.isActive && mag > kMoveDeadzone) {
|
||||
XrSpaceLocation headLoc = {XR_TYPE_SPACE_LOCATION};
|
||||
const XrSpaceLocationFlags neededHead2 =
|
||||
XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT;
|
||||
if (xr_check(xrLocateSpace(g_view_space, baseSpace, time, &headLoc),
|
||||
"xrLocateSpace(view, move)") &&
|
||||
(headLoc.locationFlags & neededHead2) == neededHead2) {
|
||||
float hfx, hfy, hfz;
|
||||
quat_rotate_vec(&headLoc.pose.orientation, 0.0f, 0.0f, -1.0f, &hfx, &hfy,
|
||||
&hfz);
|
||||
float horizLen = sqrtf(hfx * hfx + hfz * hfz);
|
||||
if (horizLen > 1e-4f) {
|
||||
hfx /= horizLen;
|
||||
hfz /= horizLen;
|
||||
// "Right" relative to forward, in the horizontal
|
||||
// plane - R_y(-90deg) applied to (hfx, hfz), see
|
||||
// xr_overlay.c's own rotation-matrix comment for
|
||||
// the same R_y() convention.
|
||||
float rfx = -hfz, rfz = hfx;
|
||||
const float kMoveSpeedMetersPerSec = 1.2f;
|
||||
float dt = (float)(nowTicks - lastMoveTicks) / 1000.0f;
|
||||
float moveX = (hfx * my + rfx * mx) * kMoveSpeedMetersPerSec * dt;
|
||||
float moveZ = (hfz * my + rfz * mx) * kMoveSpeedMetersPerSec * dt;
|
||||
float px, pz, pyaw;
|
||||
xr_session_get_player_transform(&px, &pz, &pyaw);
|
||||
// Moves the play space opposite the player's
|
||||
// intended travel, so quads (fixed in play
|
||||
// space) get closer/farther as the player
|
||||
// virtually walks toward/away from them - see
|
||||
// xr_session_set_player_transform()'s own
|
||||
// comment. Confirmed backwards on-device from
|
||||
// the initial (unverifiable-without-a-headset)
|
||||
// guess - flipped.
|
||||
xr_session_set_player_transform(px + moveX, pz + moveZ, pyaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XrActionStateGetInfo poseInfo = {XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
@@ -919,6 +1203,7 @@ void xr_input_shutdown(void) {
|
||||
g_menu_toggle_action = XR_NULL_HANDLE;
|
||||
g_space_key_action = XR_NULL_HANDLE;
|
||||
g_forward_key_action = XR_NULL_HANDLE;
|
||||
g_thumbstick_action = XR_NULL_HANDLE;
|
||||
|
||||
g_instance = XR_NULL_HANDLE;
|
||||
g_session = XR_NULL_HANDLE;
|
||||
@@ -937,4 +1222,7 @@ void xr_input_shutdown(void) {
|
||||
g_keyboard_drag.hand = -1;
|
||||
g_menu_drag.dragging = false;
|
||||
g_menu_drag.hand = -1;
|
||||
g_turn_ready = true;
|
||||
g_last_move_ticks = 0;
|
||||
g_pending_startup_recenter = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user