Rework openxr's actionset stuff.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6136 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2021-12-20 10:06:25 +00:00
parent d3f0bfea71
commit 1f01465cf3
5 changed files with 455 additions and 272 deletions

View file

@ -98,6 +98,11 @@ void QDECL Plug_Key_SetKeyBind(int bindmap, int keycode, int modifier, const cha
Key_SetBinding (keycode, modifier, newbinding, RESTRICT_LOCAL); Key_SetBinding (keycode, modifier, newbinding, RESTRICT_LOCAL);
} }
static unsigned int IN_GetKeyDest(void)
{
return key_dest_mask;
}
/* /*
static void QDECL Plug_SCR_CenterPrint(int seat, const char *text) static void QDECL Plug_SCR_CenterPrint(int seat, const char *text)

View file

@ -1094,17 +1094,17 @@ qboolean IN_SetHandPosition(const char *devname, vec3_t org, vec3_t ang, vec3_t
int dtype; int dtype;
int seat; int seat;
struct vrdevinfo_s *dev; struct vrdevinfo_s *dev;
if (!strncmp(devname, "left", 4)) if (!strncasecmp(devname, "left", 4))
{ {
seat = atoi(devname+4); seat = atoi(devname+4);
dtype = VRDEV_LEFT; dtype = VRDEV_LEFT;
} }
else if (!strncmp(devname, "right", 5)) else if (!strncasecmp(devname, "right", 5))
{ {
seat = atoi(devname+5); seat = atoi(devname+5);
dtype = VRDEV_RIGHT; dtype = VRDEV_RIGHT;
} }
else if (!strncmp(devname, "head", 4)) else if (!strncasecmp(devname, "head", 4))
{ {
seat = atoi(devname+4); seat = atoi(devname+4);
dtype = VRDEV_HEAD; dtype = VRDEV_HEAD;

View file

@ -1972,6 +1972,12 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s
Plug_Key_GetKeyBind, Plug_Key_GetKeyBind,
Plug_Key_SetKeyBind, Plug_Key_SetKeyBind,
IN_GetKeyDest,
IN_KeyEvent,
IN_MouseMove,
IN_JoystickAxisEvent,
IN_Accelerometer,
IN_Gyroscope,
IN_SetHandPosition, IN_SetHandPosition,
}; };
if (structsize == sizeof(funcs)) if (structsize == sizeof(funcs))

View file

@ -81,6 +81,7 @@ static pluginputfuncs_t *inputfuncs;
XRFUNC(xrAttachSessionActionSets) \ XRFUNC(xrAttachSessionActionSets) \
XRFUNC(xrSyncActions) \ XRFUNC(xrSyncActions) \
XRFUNC(xrGetActionStatePose) \ XRFUNC(xrGetActionStatePose) \
XRFUNC(xrApplyHapticFeedback) \
XRFUNC(xrLocateSpace) \ XRFUNC(xrLocateSpace) \
XRFUNC(xrGetActionStateBoolean) \ XRFUNC(xrGetActionStateBoolean) \
XRFUNC(xrGetActionStateFloat) \ XRFUNC(xrGetActionStateFloat) \
@ -140,6 +141,9 @@ static void XR_SetupInputs_Instance(void);
#define METRES_TO_QUAKE(x) ((x)*xr_metresize->value) #define METRES_TO_QUAKE(x) ((x)*xr_metresize->value)
#define QUAKE_TO_METRES(x) ((x)/xr_metresize->value) #define QUAKE_TO_METRES(x) ((x)/xr_metresize->value)
#define SECONDS_TO_NANOSECONDS(x) ((x)*1000000000)
#define NANOSECONDS_TO_SECONDS(x) ((x)/1000000000.0)
static void XR_PoseToAngOrg(const XrPosef *pose, vec3_t ang, vec3_t org) static void XR_PoseToAngOrg(const XrPosef *pose, vec3_t ang, vec3_t org)
{ {
XrQuaternionf q = pose->orientation; XrQuaternionf q = pose->orientation;
@ -163,63 +167,13 @@ static void XR_PoseToAngOrg(const XrPosef *pose, vec3_t ang, vec3_t org)
#endif #endif
} }
#define VectorAngles VectorAnglesPluginsSuck enum actset_e
static void VectorAngles(const float *forward, const float *up, float *result, qboolean meshpitch) //up may be NULL
{ {
float yaw, pitch, roll; AS_COMMON,
AS_MENU,
if (forward[1] == 0 && forward[0] == 0) AS_GAME,
{ MAX_ACTIONSETS
if (forward[2] > 0) };
{
pitch = -M_PI * 0.5;
yaw = up ? atan2(-up[1], -up[0]) : 0;
}
else
{
pitch = M_PI * 0.5;
yaw = up ? atan2(up[1], up[0]) : 0;
}
roll = 0;
}
else
{
yaw = atan2(forward[1], forward[0]);
pitch = -atan2(forward[2], sqrt (forward[0]*forward[0] + forward[1]*forward[1]));
if (up)
{
vec_t cp = cos(pitch), sp = sin(pitch);
vec_t cy = cos(yaw), sy = sin(yaw);
vec3_t tleft, tup;
tleft[0] = -sy;
tleft[1] = cy;
tleft[2] = 0;
tup[0] = sp*cy;
tup[1] = sp*sy;
tup[2] = cp;
roll = -atan2(DotProduct(up, tleft), DotProduct(up, tup));
}
else
roll = 0;
}
pitch *= 180 / M_PI;
yaw *= 180 / M_PI;
roll *= 180 / M_PI;
// if (meshpitch)
// pitch *= r_meshpitch.value;
if (pitch < 0)
pitch += 360;
if (yaw < 0)
yaw += 360;
if (roll < 0)
roll += 360;
result[0] = pitch;
result[1] = yaw;
result[2] = roll;
}
static struct static struct
{ {
@ -249,7 +203,7 @@ static struct
image_t *swapimages; image_t *swapimages;
} eye[MAX_VIEW_COUNT]; //note that eye is a vauge term. } eye[MAX_VIEW_COUNT]; //note that eye is a vauge term.
XrActiveActionSet actionset; XrActiveActionSet actionset[MAX_ACTIONSETS];
qboolean timeknown; qboolean timeknown;
XrTime time; XrTime time;
@ -259,8 +213,10 @@ static struct
int colourformat; int colourformat;
unsigned int numactions; unsigned int numactions;
unsigned int maxactions;
struct struct
{ {
enum actset_e set;
XrActionType acttype; XrActionType acttype;
const char *actname; //doubles up as command names for buttons const char *actname; //doubles up as command names for buttons
const char *actdescription; //user-visible string (exposed via openxr runtime somehow) const char *actdescription; //user-visible string (exposed via openxr runtime somehow)
@ -270,7 +226,7 @@ static struct
XrPath path; //for querying. XrPath path; //for querying.
XrSpace space; //for poses. XrSpace space; //for poses.
qboolean held; //for buttons. qboolean held; //for buttons.
} actions[256]; } *actions;
qboolean inputsdirty; //mostly for printing them. qboolean inputsdirty; //mostly for printing them.
#ifdef XR_EXT_hand_tracking #ifdef XR_EXT_hand_tracking
@ -930,7 +886,37 @@ static qboolean XR_Init(vrsetup_t *qreqs, rendererstate_t *info)
return true; return true;
} }
static XrAction XR_DefineAction(XrActionType type, const char *name, const char *description, const char *root) static qboolean XR_HapticCommand_f(qboolean isinsecure)
{
size_t u;
char actionname[XR_MAX_ACTION_NAME_SIZE];
cmdfuncs->Argv(0, actionname, sizeof(actionname));
for (u = 0; u < xr.numactions; u++)
{
if (!strcasecmp(xr.actions[u].actname, actionname) && xr.actions[u].acttype == XR_ACTION_TYPE_VIBRATION_OUTPUT)
{
if (xr.session)
{
XrHapticActionInfo info = {XR_TYPE_HAPTIC_ACTION_INFO};
XrHapticVibration haptic = {XR_TYPE_HAPTIC_VIBRATION};
info.action = xr.actions[u].action;
info.subactionPath = XR_NULL_PATH;
cmdfuncs->Argv(1, actionname, sizeof(actionname));
haptic.duration = *actionname?SECONDS_TO_NANOSECONDS(atof(actionname)):XR_MIN_HAPTIC_DURATION;
cmdfuncs->Argv(2, actionname, sizeof(actionname));
haptic.amplitude = *actionname?atof(actionname):0;
cmdfuncs->Argv(3, actionname, sizeof(actionname));
haptic.frequency = *actionname?atof(actionname):XR_FREQUENCY_UNSPECIFIED;
xrApplyHapticFeedback(xr.session, &info, (XrHapticBaseHeader*)&haptic);
}
return true;
}
}
return false;
}
static XrAction XR_DefineAction(enum actset_e set, XrActionType type, const char *name, const char *description, const char *root)
{ {
XrActionCreateInfo info = {XR_TYPE_ACTION_CREATE_INFO}; XrActionCreateInfo info = {XR_TYPE_ACTION_CREATE_INFO};
XrResult res; XrResult res;
@ -941,18 +927,31 @@ static XrAction XR_DefineAction(XrActionType type, const char *name, const char
int dconflicts = 0; int dconflicts = 0;
for (u = 0; u < xr.numactions; u++) for (u = 0; u < xr.numactions; u++)
{ {
if (xr.actions[u].acttype == type && !strcmp(xr.actions[u].actname, name) /*&& !strcmp(xr.actions[u].actdescription, description)*/ && !strcmp(xr.actions[u].subactionpath?xr.actions[u].subactionpath:"", root?root:"")) if (xr.actions[u].set != set)
continue;
if ((xr.actions[u].acttype == type || !type) && !strcmp(xr.actions[u].actname, name) /*&& !strcmp(xr.actions[u].actdescription, description)*/ && !strcmp(xr.actions[u].subactionpath?xr.actions[u].subactionpath:"", root?root:""))
{ //looks like a dupe... { //looks like a dupe...
return xr.actions[u].action; return xr.actions[u].action;
} }
if (!strcasecmp(xr.actions[u].actname, name)) if (!strcasecmp(xr.actions[u].actname, name))
nconflicts++; //arse balls knob cock nconflicts++; //arse balls knob cock
if (!strcasecmp(xr.actions[u].actdescription, description)) if (description && !strcasecmp(xr.actions[u].actdescription, description))
dconflicts++; //arse balls knob cock dconflicts++; //arse balls knob cock
} }
if (xr.numactions == countof(xr.actions)) if (!description)
return XR_NULL_HANDLE; //nope, list full. sorry. return XR_NULL_HANDLE; //none found
if (u == xr.maxactions)
{
size_t nm = (xr.maxactions+1)*2;
void *n = plugfuncs->Realloc(xr.actions, sizeof(*xr.actions) * nm);
if (!n)
return XR_NULL_HANDLE; //erk!
xr.actions = n;
xr.maxactions = nm;
}
memset(&xr.actions[u], 0, sizeof(xr.actions[u]));
xr.actions[u].set = set;
xr.actions[u].acttype = type; xr.actions[u].acttype = type;
xr.actions[u].actname = strdup(name); xr.actions[u].actname = strdup(name);
xr.actions[u].actdescription = strdup(description); xr.actions[u].actdescription = strdup(description);
@ -974,16 +973,29 @@ static XrAction XR_DefineAction(XrActionType type, const char *name, const char
info.subactionPaths = &xr.actions[u].path; info.subactionPaths = &xr.actions[u].path;
} }
info.actionType = xr.actions[u].acttype; info.actionType = xr.actions[u].acttype;
if (*xr.actions[u].actname == '+') Q_strlcpy(info.actionName, xr.actions[u].actname, sizeof(info.actionName));
Q_strlcpy(info.actionName, xr.actions[u].actname+1, sizeof(info.actionName)); for (ffs = info.actionName; *ffs; ffs++)
else {
Q_strlcpy(info.actionName, xr.actions[u].actname, sizeof(info.actionName)); if (*ffs >= 'A' && *ffs < 'Z')
while ((ffs=strchr(info.actionName, ' '))) *ffs = '_'; //convert spaces to underscores. *ffs += 'a'-'A'; //must be lower-case
else if (*ffs >= 'a' && *ffs <= 'z')
; //allowed
else if (*ffs >= '0' && *ffs <= '9')
; //allowed
else if (*ffs == '.' || *ffs == '-' || *ffs == '_')
; //allowed
// '/' is not allowed as it must be a single segment.
else
*ffs = '_'; //everything else is blocked
}
Q_strlcpy(info.localizedActionName, xr.actions[u].actdescription, sizeof(info.localizedActionName)); Q_strlcpy(info.localizedActionName, xr.actions[u].actdescription, sizeof(info.localizedActionName));
res = xrCreateAction(xr.actionset.actionSet, &info, &xr.actions[u].action); res = xrCreateAction(xr.actionset[set].actionSet, &info, &xr.actions[u].action);
if (XR_FAILED(res)) if (XR_FAILED(res))
Con_Printf("openxr: Unable to create action %s [%s] - %s\n", info.actionName, info.localizedActionName, XR_StringForResult(res)); Con_Printf("openxr: Unable to create action %s [%s] - %s\n", info.actionName, info.localizedActionName, XR_StringForResult(res));
if (info.actionType == XR_ACTION_TYPE_VIBRATION_OUTPUT)
cmdfuncs->AddCommand(xr.actions[u].actname);
return xr.actions[u].action; return xr.actions[u].action;
} }
@ -1025,8 +1037,8 @@ static int XR_BindProfileStr(const char *fname, const char *file)
{ {
XrAction act; XrAction act;
XrResult res; XrResult res;
XrPath path; XrPath profilepath = XR_NULL_PATH;
char line[1024]; char line[1024], *linestart;
char name[1024]; char name[1024];
char type[256]; char type[256];
char desc[1024]; char desc[1024];
@ -1034,82 +1046,157 @@ static int XR_BindProfileStr(const char *fname, const char *file)
char root[1024]; char root[1024];
unsigned int p; unsigned int p;
char prefix[2][1024]; char prefix[2][1024];
enum actset_e set;
XrInteractionProfileSuggestedBinding suggestedbindings = {XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
unsigned int acts = 0;
XrActionSuggestedBinding bindings[256];
unsigned int totalacts = 0;
//first line is eg: /interaction_profiles/khr/simple_controller
while (XR_ReadLine(&file, line, sizeof(line))) while (XR_ReadLine(&file, line, sizeof(line)))
{ {
cmdfuncs->TokenizeString(line); set = AS_COMMON;
if (cmdfuncs->Argc()) linestart = line;
break; while (*linestart == ' ' || *linestart == '\t')
} linestart++;
cmdfuncs->Argv(0, name, sizeof(name));
for (p = 0; p < countof(prefix); p++)
cmdfuncs->Argv(p+1, prefix[p], sizeof(prefix[p]));
if (*name && XR_SUCCEEDED(xrStringToPath(xr.instance, name, &path)))
{ //okay, it accepted that path at least...
XrInteractionProfileSuggestedBinding suggestedbindings = {XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
unsigned int acts = 0;
XrActionSuggestedBinding bindings[256];
while (XR_ReadLine(&file, line, sizeof(line))) if (!strncasecmp(linestart, "menu:", 5))
{ {
cmdfuncs->TokenizeString(line); set = AS_MENU;
if (cmdfuncs->Argc()) linestart+=5;
}
else if (!strncasecmp(linestart, "game:", 5))
{
set = AS_GAME;
linestart+=5;
}
else if (!strncasecmp(linestart, "common:", 7))
{ //the default, but nice to be able to be explicit (especially if you want a colon in the action name).
set = AS_COMMON;
linestart+=7;
}
cmdfuncs->TokenizeString(linestart);
if (cmdfuncs->Argc())
{
cmdfuncs->Argv(0, name, sizeof(name));
if (!strcasecmp(name, "dev"))
{ {
cmdfuncs->Argv(0, name, sizeof(name)); cmdfuncs->Argv(1, root, sizeof(root));
continue;
}
else if (!strcasecmp(name, "profile"))
{
if (acts)
{
suggestedbindings.interactionProfile = profilepath;
suggestedbindings.countSuggestedBindings = acts;
suggestedbindings.suggestedBindings = bindings;
res = xrSuggestInteractionProfileBindings(xr.instance, &suggestedbindings);
if (XR_FAILED(res))
Con_Printf(CON_ERROR"%s: xrSuggestInteractionProfileBindings failed - %s\n", fname, XR_StringForResult(res));
totalacts += acts;
acts = 0;
}
cmdfuncs->Argv(1, name, sizeof(name));
for (p = 0; p < countof(prefix); p++)
cmdfuncs->Argv(2+p, prefix[p], sizeof(prefix[p]));
*root = 0;
if (XR_FAILED(xrStringToPath(xr.instance, name, &profilepath)))
profilepath = XR_NULL_PATH;
continue;
}
else if (!strcasecmp(name, "action"))
{
cmdfuncs->Argv(1, name, sizeof(name));
cmdfuncs->Argv(2, desc, sizeof(desc));
cmdfuncs->Argv(3, type, sizeof(type));
cmdfuncs->Argv(4, bind, sizeof(bind));
}
else if (cmdfuncs->Argc() == 2)
{
*desc = 0;
*type = 0;
cmdfuncs->Argv(1, bind, sizeof(desc));
}
else
{
if (cmdfuncs->Argc() < 4)
{
Con_Printf("Unknown action command \"%s\"\n", name);
continue;
}
cmdfuncs->Argv(1, desc, sizeof(desc)); cmdfuncs->Argv(1, desc, sizeof(desc));
cmdfuncs->Argv(2, type, sizeof(type)); cmdfuncs->Argv(2, type, sizeof(type));
cmdfuncs->Argv(3, bind, sizeof(bind)); cmdfuncs->Argv(3, bind, sizeof(bind));
cmdfuncs->Argv(4, root, sizeof(root)); if (cmdfuncs->Argc() >= 5)
Con_Printf("%s: %s: Extra tokens found\n", fname, name);
}
if (*type) if (*type)
{
XrActionType xrtype;
if (!strcasecmp(type, "button"))
xrtype = XR_ACTION_TYPE_BOOLEAN_INPUT;
else if (!strcasecmp(type, "float"))
xrtype = XR_ACTION_TYPE_FLOAT_INPUT;
else if (!strcasecmp(type, "vector2f"))
xrtype = XR_ACTION_TYPE_VECTOR2F_INPUT;
else if (!strcasecmp(type, "pose"))
xrtype = XR_ACTION_TYPE_POSE_INPUT;
else if (!strcasecmp(type, "vibration") || !strcasecmp(type, "haptic"))
xrtype = XR_ACTION_TYPE_VIBRATION_OUTPUT;
else
continue;
//define our action...
act = XR_DefineAction(set, xrtype, name, desc, root);
}
else
{
act = XR_DefineAction(set, (XrActionType)0, name, NULL, root);
if(act==XR_NULL_HANDLE)
Con_Printf("Action %s not defined yet\n", name);
}
//and add it to the profile we're building.
if (act != XR_NULL_HANDLE && *bind && profilepath!=XR_NULL_PATH)
{
if (*bind == '/')
{ {
XrActionType xrtype; res = xrStringToPath(xr.instance, bind, &bindings[acts].binding);
if (!strcasecmp(type, "button")) if (XR_SUCCEEDED(res) && acts < countof(bindings))
xrtype = XR_ACTION_TYPE_BOOLEAN_INPUT; bindings[acts++].action = act;
else if (!strcasecmp(type, "float")) }
xrtype = XR_ACTION_TYPE_FLOAT_INPUT; else if (*root == '/')
else if (!strcasecmp(type, "vector2f")) {
xrtype = XR_ACTION_TYPE_VECTOR2F_INPUT; res = xrStringToPath(xr.instance, va("%s/%s", root, bind), &bindings[acts].binding);
else if (!strcasecmp(type, "pose")) if (XR_SUCCEEDED(res) && acts < countof(bindings))
xrtype = XR_ACTION_TYPE_POSE_INPUT; bindings[acts++].action = act;
else if (!strcasecmp(type, "vibration")) }
xrtype = XR_ACTION_TYPE_VIBRATION_OUTPUT; else for (p = 0; p < countof(prefix) && *prefix[p]=='/'; p++)
else {
continue; res = xrStringToPath(xr.instance, va("%s%s", prefix[p], bind), &bindings[acts].binding);
if (XR_SUCCEEDED(res) && acts < countof(bindings))
act = XR_DefineAction(xrtype, name, desc, root); bindings[acts++].action = act;
if (act != XR_NULL_HANDLE && *bind)
{
if (*bind == '/')
{
res = xrStringToPath(xr.instance, bind, &bindings[acts].binding);
if (XR_SUCCEEDED(res))
bindings[acts++].action = act;
}
else for (p = 0; p < countof(prefix) && *prefix[p]=='/'; p++)
{
res = xrStringToPath(xr.instance, va("%s%s", prefix[p], bind), &bindings[acts].binding);
if (XR_SUCCEEDED(res))
bindings[acts++].action = act;
}
}
} }
} }
} }
if (acts)
{
suggestedbindings.interactionProfile = path;
suggestedbindings.countSuggestedBindings = acts;
suggestedbindings.suggestedBindings = bindings;
res = xrSuggestInteractionProfileBindings(xr.instance, &suggestedbindings);
if (XR_FAILED(res))
Con_Printf("%s: xrSuggestInteractionProfileBindings failed - %s\n", fname, XR_StringForResult(res));
return acts;
}
} }
return 0;
if (acts)
{
suggestedbindings.interactionProfile = profilepath;
suggestedbindings.countSuggestedBindings = acts;
suggestedbindings.suggestedBindings = bindings;
res = xrSuggestInteractionProfileBindings(xr.instance, &suggestedbindings);
if (XR_FAILED(res))
Con_Printf(CON_ERROR"%s: xrSuggestInteractionProfileBindings failed - %s\n", fname, XR_StringForResult(res));
totalacts += acts;
}
return totalacts;
} }
static int QDECL XR_BindProfileFile(const char *fname, qofs_t fsize, time_t mtime, void *ctx, struct searchpathfuncs_s *package) static int QDECL XR_BindProfileFile(const char *fname, qofs_t fsize, time_t mtime, void *ctx, struct searchpathfuncs_s *package)
@ -1135,19 +1222,24 @@ static const struct
} xr_knownprofiles[] = } xr_knownprofiles[] =
{ {
//FIXME: set up some proper bindings! //FIXME: set up some proper bindings!
{"khr_simple", "/interaction_profiles/khr/simple_controller /user/hand/left/ /user/hand/right/\n" {"khr_simple", "profile /interaction_profiles/khr/simple_controller /user/hand/left/ /user/hand/right/\n"
"+attack_left \"Left Attack\" button input/select/click /user/hand/left\n" "dev /user/hand/left\n"
"+attack_right \"Right Attack\" button input/select/click /user/hand/right\n" "+attack_left \"Left Attack\" button input/select/click\n"
"+menu_left \"Left Menu\" button input/menu/click /user/hand/left\n" "+menu_left \"Left Menu\" button input/menu/click\n"
"+menu_right \"Right Menu\" button input/menu/click /user/hand/right\n" "#POSE_LEFT \"Left Aim Pose\" pose input/aim/pose\n"
"left_aim \"Left Aim Pose\" pose input/grip/pose /user/hand/left\n" //"left_grip \"Left Grip Pose\" pose input/grip/pose\n"
"right_aim \"Right Aim Pose\" pose input/grip/pose /user/hand/right\n" "haptic_left \"Left Haptic\" haptic output/haptic\n"
// "grip_pose \"Grip Pose\" pose input/grip/pose\n" "dev /user/hand/right\n"
// "aim_pose \"Aim Pose\" pose input/aim/pose\n" "menu: #GP_START \"Menu Enter\" button input/select/click\n"
"vibrate \"A Vibrator\" vibration output/haptic\n" "menu: #GP_BACK \"Menu Escape\" button input/menu/click\n"
"game: +attack_right \"Right Attack\" button input/select/click\n"
"game: +menu_right \"Right Menu\" button input/menu/click\n"
"#POSE_RIGHT \"Right Aim Pose\" pose input/aim/pose\n"
//"right_grip \"Right Grip Pose\" pose input/grip/pose\n"
"haptic_right \"Right Haptic\" haptic output/haptic\n"
}, },
/* {"valve_index", "/interaction_profiles/valve/index_controller /user/hand/left/ /user/hand/right/\n" /* {"valve_index", "profile /interaction_profiles/valve/index_controller /user/hand/left/ /user/hand/right/\n"
//"unbound \"Unused Button\" button input/system/click\n" //"unbound \"Unused Button\" button input/system/click\n"
//"unbound \"Unused Button\" button input/system/touch\n" //"unbound \"Unused Button\" button input/system/touch\n"
//"unbound \"Unused Button\" button input/a/click\n" //"unbound \"Unused Button\" button input/a/click\n"
@ -1167,10 +1259,10 @@ static const struct
//"unbound \"Unused Button\" button input/trackpad/touch\n" //"unbound \"Unused Button\" button input/trackpad/touch\n"
//"unbound \"Unused Button\" pose input/grip/pose\n" //"unbound \"Unused Button\" pose input/grip/pose\n"
//"unbound \"Unused Button\" pose input/aim/pose\n" //"unbound \"Unused Button\" pose input/aim/pose\n"
//"unbound \"Unused Button\" vibration output/haptic\n" //"unbound \"Unused Button\" haptic output/haptic\n"
}, },
*/ */
/* {"htc_vive", "/interaction_profiles/htc/vive_controller /user/hand/left/ /user/hand/right/\n" /* {"htc_vive", "profile /interaction_profiles/htc/vive_controller /user/hand/left/ /user/hand/right/\n"
//"unbound \"Unused Button\" button input/system/click\n" //"unbound \"Unused Button\" button input/system/click\n"
//"unbound \"Unused Button\" button input/squeeze/click\n" //"unbound \"Unused Button\" button input/squeeze/click\n"
//"unbound \"Unused Button\" button input/menu/click\n" //"unbound \"Unused Button\" button input/menu/click\n"
@ -1181,10 +1273,10 @@ static const struct
//"unbound \"Unused Button\" button input/trackpad/touch\n" //"unbound \"Unused Button\" button input/trackpad/touch\n"
//"unbound \"Unused Button\" pose input/grip/pose\n" //"unbound \"Unused Button\" pose input/grip/pose\n"
//"unbound \"Unused Button\" pose input/aim/pose\n" //"unbound \"Unused Button\" pose input/aim/pose\n"
//"unbound \"Unused Button\" vibration output/haptic\n" //"unbound \"Unused Button\" haptic output/haptic\n"
); );
*/ */
/* {"htc_vive_pro", "/interaction_profiles/htc/vive_pro /user/head/\n" /* {"htc_vive_pro", "profile /interaction_profiles/htc/vive_pro /user/head/\n"
//"unbound \"Unused Button\" button input/system/click\n" //"unbound \"Unused Button\" button input/system/click\n"
//"unbound \"Unused Button\" button input/volume_up/click\n" //"unbound \"Unused Button\" button input/volume_up/click\n"
//"unbound \"Unused Button\" button input/volume_down/click\n" //"unbound \"Unused Button\" button input/volume_down/click\n"
@ -1192,32 +1284,32 @@ static const struct
); );
*/ */
//FIXME: map to quake's keys. //just map everything to quake's various buttons so that mods with proper gamepad mapping will work here too.
{"gamepad", "/interaction_profiles/microsoft/xbox_controller /user/gamepad/\n" {"gamepad", "profile /interaction_profiles/microsoft/xbox_controller /user/gamepad/\n"
"togglemenu Menu button input/menu/click\n" "#GP_START \"Start\" button input/menu/click\n"
//"unbound \"Unused Button\" button input/view/click\n" "#GP_BACK \"Back\" button input/view/click\n"
//"unbound \"Unused Button\" button input/a/click\n" "#GP_A \"A Button\" button input/a/click\n"
//"unbound \"Unused Button\" button input/b/click\n" "#GP_B \"B Button\" button input/b/click\n"
//"unbound \"Unused Button\" button input/x/click\n" "#GP_X \"X Button\" button input/x/click\n"
//"unbound \"Unused Button\" button input/y/click\n" "#GP_Y \"Y Button\" button input/y/click\n"
"+back \"Move Backwards\" button input/dpad_down/click\n" "#GP_DPAD_DOWN \"Move Backwards\" button input/dpad_down/click\n"
"+moveright \"Move Right\" button input/dpad_right/click\n" "#GP_DPAD_RIGHT \"Move Right\" button input/dpad_right/click\n"
"+forward \"Move Forward\" button input/dpad_up/click\n" "#GP_DPAD_UP \"Move Forward\" button input/dpad_up/click\n"
"+moveleft \"Move Left\" button input/dpad_left/click\n" "#GP_DPAD_LEFT \"Move Left\" button input/dpad_left/click\n"
"+jump \"Jump\" button input/shoulder_left/click\n" "#GP_LSHOULDER \"Jump\" button input/shoulder_left/click\n"
"+attack \"Attack\" button input/shoulder_right/click\n" "#GP_RSHOULDER \"Attack\" button input/shoulder_right/click\n"
//"unbound \"Unused Button\" button input/thumbstick_left/click\n" "#GP_LTHUMB \"Left Thumb\" button input/thumbstick_left/click\n"
//"unbound \"Unused Button\" button input/thumbstick_right/click\n" "#GP_RTHUMB \"Right Thumb\" button input/thumbstick_right/click\n"
//"unbound \"Unused Axis\" float input/trigger_left/click\n" "#GP_AXIS_LTRIGGER \"Left Trigger\" float input/trigger_left/value\n"
//"unbound \"Unused Axis\" float input/trigger_right/click\n" "#GP_AXIS_RTRIGGER \"Right Trigger\" float input/trigger_right/value\n"
//"unbound \"Unused Axis\" float input/thumbstick_left/x\n" "#GP_AXIS_LEFT_X \"Left Thumbstick X\" float input/thumbstick_left/x\n"
//"unbound \"Unused Axis\" float input/thumbstick_left/y\n" "#GP_AXIS_LEFT_Y \"Left Thumbstick y\" float input/thumbstick_left/y\n"
//"unbound \"Unused Axis\" float input/thumbstick_right/x\n" "#GP_AXIS_RIGHT_X \"Right Thumbstick X\" float input/thumbstick_right/x\n"
//"unbound \"Unused Axis\" float input/thumbstick_right/y\n" "#GP_AXIS_RIGHT_X \"Right Thumbstick Y\" float input/thumbstick_right/y\n"
//"unbound \"Unused Vibrator\" vibration output/haptic_left\n" "haptic_gp_left \"Left Haptic (Main)\" haptic output/haptic_left\n"
//"unbound \"Unused Vibrator\" vibration output/haptic_left_trigger\n" "haptic_gp_left_trigger \"Left-Trigger Haptic\" haptic output/haptic_left_trigger\n"
//"unbound \"Unused Vibrator\" vibration output/haptic_right\n" "haptic_gp_right \"Right Haptic (Main)\" haptic output/haptic_right\n"
//"unbound \"Unused Vibrator\" vibration output/haptic_right_trigger\n" "haptic_gp_right_trigger \"Right-Trigger Haptic\" haptic output/haptic_right_trigger\n"
}, },
}; };
@ -1225,16 +1317,19 @@ static void XR_SetupInputs_Instance(void)
{ {
unsigned int h; unsigned int h;
XrResult res; XrResult res;
int set;
char *actionsetNames[] = {"genericactions", "menuactions", "gameactions"};
char *actionsetNamesText[] = {"Generic Actions", "Menu Actions", "Game Actions"};
//begin instance-level init for (set = 0; set < MAX_ACTIONSETS; set++)
{ {
XrActionSetCreateInfo info = {XR_TYPE_ACTION_SET_CREATE_INFO}; XrActionSetCreateInfo info = {XR_TYPE_ACTION_SET_CREATE_INFO};
Q_strlcpy(info.actionSetName, "actions", sizeof(info.actionSetName)); Q_strlcpy(info.actionSetName, actionsetNames[set], sizeof(info.actionSetName));
Q_strlcpy(info.localizedActionSetName, FULLENGINENAME" Actions", sizeof(info.localizedActionSetName)); Q_strlcpy(info.localizedActionSetName, actionsetNamesText[set], sizeof(info.localizedActionSetName));
info.priority = 0; info.priority = 0;
xr.actionset.subactionPath = XR_NULL_PATH; xr.actionset[set].subactionPath = XR_NULL_PATH;
res = xrCreateActionSet(xr.instance, &info, &xr.actionset.actionSet); res = xrCreateActionSet(xr.instance, &info, &xr.actionset[set].actionSet);
if (XR_FAILED(res)) if (XR_FAILED(res))
Con_Printf("openxr: Unable to create actionset - %s\n", XR_StringForResult(res)); Con_Printf("openxr: Unable to create actionset - %s\n", XR_StringForResult(res));
} }
@ -1279,8 +1374,12 @@ static void XR_SetupInputs_Session(void)
//and attach it. //and attach it.
{ {
XrSessionActionSetsAttachInfo info = {XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; XrSessionActionSetsAttachInfo info = {XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO};
info.countActionSets = 1; XrActionSet sets[MAX_ACTIONSETS];
info.actionSets = &xr.actionset.actionSet; unsigned int set;
for (set = 0; set < MAX_ACTIONSETS; set++)
if (xr.actionset[set].actionSet)
sets[info.countActionSets++] = xr.actionset[set].actionSet;
info.actionSets = sets;
res = xrAttachSessionActionSets(xr.session, &info); res = xrAttachSessionActionSets(xr.session, &info);
if (XR_FAILED(res)) if (XR_FAILED(res))
Con_Printf("openxr: xrAttachSessionActionSets failed - %s\n", XR_StringForResult(res)); Con_Printf("openxr: xrAttachSessionActionSets failed - %s\n", XR_StringForResult(res));
@ -1305,76 +1404,83 @@ static void XR_SetupInputs_Session(void)
static void XR_PrintInputs(void) static void XR_PrintInputs(void)
{ {
XrResult res; XrResult res;
if (xr_debug->ival) XrInteractionProfileState profile = {XR_TYPE_INTERACTION_PROFILE_STATE};
XrPath path;
unsigned int u;
static const char *paths[] = {"/user/hand/left", "/user/hand/right", "/user/head", "/user/gamepad"};
Con_Printf("OpenXR Interaction Profiles:\n");
for (u = 0; u < countof(paths); u++)
{ {
XrInteractionProfileState profile = {XR_TYPE_INTERACTION_PROFILE_STATE}; xrStringToPath(xr.instance, paths[u], &path);
XrPath path; res = xrGetCurrentInteractionProfile(xr.session, path, &profile);
unsigned int u; if (XR_SUCCEEDED(res))
static const char *paths[] = {"/user/hand/left", "/user/hand/right", "/user/head", "/user/gamepad", "/user/treadmill", "/user/"};
Con_Printf("OpenXR Interaction Profiles:\n");
for (u = 0; u < countof(paths); u++)
{ {
xrStringToPath(xr.instance, paths[u], &path); char buf[256];
res = xrGetCurrentInteractionProfile(xr.session, path, &profile); uint32_t len = sizeof(buf);
if (XR_SUCCEEDED(res)) if (!profile.interactionProfile)
{ Con_Printf("\t%s: "S_COLOR_GRAY"no profile/device\n", paths[u]);
char buf[256];
uint32_t len = sizeof(buf);
if (!profile.interactionProfile)
Con_Printf("\t%s: "S_COLOR_GRAY"no profile/device\n", paths[u]);
else
{
res = xrPathToString(xr.instance, profile.interactionProfile, sizeof(buf), &len, buf);
Con_Printf("\t%s: "S_COLOR_GREEN"%s\n", paths[u], buf);
}
}
}
Con_Printf("Bound actions:\n");
for (u = 0; u < xr.numactions; u++)
{
XrBoundSourcesForActionEnumerateInfo info = {XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO};
uint32_t inputs, i, bufsize;
XrPath input[20];
info.action = xr.actions[u].action;
res = xrEnumerateBoundSourcesForAction(xr.session, &info, countof(input), &inputs, input);
if (XR_SUCCEEDED(res))
{
Con_Printf("\t%s:\n", xr.actions[u].actname);
if (!inputs)
Con_Printf(S_COLOR_GRAY"\t(unbound)\n");
else for (i = 0; i < inputs; i++)
{
char buffer[8192];
XrInputSourceLocalizedNameGetInfo info = {XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO};
info.sourcePath = input[i];
info.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT| //'left hand'
XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT| //'foo controller'
XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT; //'trigger'
res = xrGetInputSourceLocalizedName(xr.session, &info, sizeof(buffer), &bufsize, buffer);
if (XR_FAILED(res))
Q_snprintf(buffer, sizeof(buffer), "error %i", res);
Con_Printf(S_COLOR_GREEN"\t\t%s\n", buffer);
}
}
else if (res == XR_ERROR_HANDLE_INVALID) //monado reports this for unimplemented things.
Con_Printf("\t%s: error XR_ERROR_HANDLE_INVALID (not implemented?)\n", xr.actions[u].actname);
else else
Con_Printf("\t%s: error %s\n", xr.actions[u].actname, XR_StringForResult(res)); {
res = xrPathToString(xr.instance, profile.interactionProfile, sizeof(buf), &len, buf);
Con_Printf("\t%s: "S_COLOR_GREEN"%s\n", paths[u], buf);
}
} }
} }
Con_Printf("Bound actions:\n");
for (u = 0; u < xr.numactions; u++)
{
XrBoundSourcesForActionEnumerateInfo info = {XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO};
uint32_t inputs, i, bufsize;
XrPath input[20];
info.action = xr.actions[u].action;
res = xrEnumerateBoundSourcesForAction(xr.session, &info, countof(input), &inputs, input);
if (XR_SUCCEEDED(res))
{
Con_Printf("\t%s "S_COLOR_GRAY"(%s)"S_COLOR_WHITE":\n", xr.actions[u].actname, xr.actions[u].actdescription);
if (!inputs)
Con_Printf(S_COLOR_GRAY"\t\t(unbound)\n");
else for (i = 0; i < inputs; i++)
{
char buffer[8192];
XrInputSourceLocalizedNameGetInfo info = {XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO};
info.sourcePath = input[i];
info.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT| //'left hand'
XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT| //'foo controller'
XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT; //'trigger'
res = xrGetInputSourceLocalizedName(xr.session, &info, sizeof(buffer), &bufsize, buffer);
if (XR_FAILED(res))
Q_snprintf(buffer, sizeof(buffer), S_COLOR_RED"error %i", res);
else
Con_Printf(S_COLOR_GREEN"\t\t%s\n", buffer);
}
}
else if (res == XR_ERROR_HANDLE_INVALID) //monado reports this for unimplemented things.
Con_Printf(S_COLOR_RED"\t%s: error XR_ERROR_HANDLE_INVALID (not implemented?)\n", xr.actions[u].actname);
else
Con_Printf(S_COLOR_RED"\t%s: error %s\n", xr.actions[u].actname, XR_StringForResult(res));
}
} }
static void XR_UpdateInputs(XrTime time) static void XR_UpdateInputs(XrTime time)
{ {
XrResult res; XrResult res;
unsigned int h; size_t h;
qboolean activesets[MAX_ACTIONSETS] = {true};
if (inputfuncs->GetKeyDest() & ~kdm_game)
activesets[AS_MENU] = true; //used while looking at the menuqc/emenus/console/etc.
else
activesets[AS_GAME] = true; //used while the game has focus.
{ {
XrActionsSyncInfo syncinfo = {XR_TYPE_ACTIONS_SYNC_INFO}; XrActionsSyncInfo syncinfo = {XR_TYPE_ACTIONS_SYNC_INFO};
syncinfo.countActiveActionSets = 1; XrActiveActionSet sets[MAX_ACTIONSETS];
syncinfo.activeActionSets = &xr.actionset; for (h = 0; h < MAX_ACTIONSETS; h++)
if (activesets[h])
sets[syncinfo.countActiveActionSets++] = xr.actionset[h];
syncinfo.activeActionSets = sets;
res = xrSyncActions(xr.session, &syncinfo); res = xrSyncActions(xr.session, &syncinfo);
if (res == XR_SESSION_NOT_FOCUSED) if (res == XR_SESSION_NOT_FOCUSED)
; //handle it anyway, giving us a chance to disable various inputs. ; //handle it anyway, giving us a chance to disable various inputs.
@ -1386,7 +1492,7 @@ static void XR_UpdateInputs(XrTime time)
{ {
if (xr.actions[h].action == XR_NULL_HANDLE) //failed to init if (xr.actions[h].action == XR_NULL_HANDLE) //failed to init
continue; continue;
switch(xr.actions[h].acttype) safeswitch(xr.actions[h].acttype)
{ {
case XR_ACTION_TYPE_POSE_INPUT: case XR_ACTION_TYPE_POSE_INPUT:
{ {
@ -1395,32 +1501,53 @@ static void XR_UpdateInputs(XrTime time)
info.action = xr.actions[h].action; info.action = xr.actions[h].action;
info.subactionPath = xr.actions[h].path; info.subactionPath = xr.actions[h].path;
res = xrGetActionStatePose(xr.session, &info, &pose); if (XR_FAILED(xrGetActionStatePose(xr.session, &info, &pose)))
if (pose.isActive) break;
if (pose.isActive && activesets[xr.actions[h].set])
{ //its mapped to something, woo. { //its mapped to something, woo.
XrSpaceVelocity vel = {XR_TYPE_SPACE_VELOCITY}; XrSpaceVelocity vel = {XR_TYPE_SPACE_VELOCITY};
XrSpaceLocation loc = {XR_TYPE_SPACE_LOCATION, &vel}; XrSpaceLocation loc = {XR_TYPE_SPACE_LOCATION, &vel};
vec3_t transform[4], angles, lvel, avel; vec3_t angles, org, lvel, avel;
res = xrLocateSpace(xr.actions[h].space, xr.space, time, &loc); res = xrLocateSpace(xr.actions[h].space, xr.space, time, &loc);
// XR_PoseToTransform(&loc.pose, transform); XR_PoseToAngOrg(&loc.pose, angles, org);
XR_PoseToAngOrg(&loc.pose, angles, transform[3]);
// VectorAngles(transform[0], transform[2], angles, false);
VectorSet(lvel, vel.linearVelocity.x, vel.linearVelocity.y, vel.linearVelocity.z); VectorSet(lvel, vel.linearVelocity.x, vel.linearVelocity.y, vel.linearVelocity.z);
VectorSet(avel, vel.angularVelocity.x, vel.angularVelocity.y, vel.angularVelocity.z); VectorSet(avel, vel.angularVelocity.x, vel.angularVelocity.y, vel.angularVelocity.z);
if (!inputfuncs->SetHandPosition(xr.actions[h].actname, if (!strncasecmp(xr.actions[h].actname, "#POSE_", 6))
(loc.locationFlags&XR_SPACE_LOCATION_POSITION_VALID_BIT)?transform[3]:NULL, {
(loc.locationFlags&XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)?angles:NULL, if (inputfuncs->SetHandPosition(xr.actions[h].actname+6,
(vel.velocityFlags&XR_SPACE_VELOCITY_LINEAR_VALID_BIT)?lvel:NULL, (loc.locationFlags&XR_SPACE_LOCATION_POSITION_VALID_BIT)?org:NULL,
(vel.velocityFlags&XR_SPACE_VELOCITY_ANGULAR_VALID_BIT)?avel:NULL)) (loc.locationFlags&XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)?angles:NULL,
(vel.velocityFlags&XR_SPACE_VELOCITY_LINEAR_VALID_BIT)?lvel:NULL,
(vel.velocityFlags&XR_SPACE_VELOCITY_ANGULAR_VALID_BIT)?avel:NULL))
;
else
Con_Printf("Pose \"%s\" for action \"%s\" is not a known pose name\n", xr.actions[h].actname+1, xr.actions[h].actdescription);
}
else
{ //custom poses that mods might want to handle themselves... { //custom poses that mods might want to handle themselves...
vec3_t angles;
char cmd[256]; char cmd[256];
VectorAngles(transform[0], transform[2], angles, false); unsigned int status = 0;
status |= (loc.locationFlags&XR_SPACE_LOCATION_POSITION_VALID_BIT)?VRSTATUS_ORG:0;
Q_snprintf(cmd, sizeof(cmd), "%s %g %g %g %g %g %g %g %g %g %g %g %g\n", xr.actions[h].actname, status |= (loc.locationFlags&XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)?VRSTATUS_ANG:0;
angles[0], angles[1], angles[2], transform[3][0], transform[3][1], transform[3][2], status |= (vel.velocityFlags&XR_SPACE_VELOCITY_LINEAR_VALID_BIT)?VRSTATUS_VEL:0;
status |= (vel.velocityFlags&XR_SPACE_VELOCITY_ANGULAR_VALID_BIT)?VRSTATUS_AVEL:0;
if (status & (VRSTATUS_VEL|VRSTATUS_AVEL))
{
Q_snprintf(cmd, sizeof(cmd), "%s %u %g %g %g %g %g %g %g %g %g %g %g %g\n", xr.actions[h].actname, status,
angles[0], angles[1], angles[2], org[0], org[1], org[2],
vel.angularVelocity.x, vel.angularVelocity.y, vel.angularVelocity.z, vel.linearVelocity.x, vel.linearVelocity.y, vel.linearVelocity.z); vel.angularVelocity.x, vel.angularVelocity.y, vel.angularVelocity.z, vel.linearVelocity.x, vel.linearVelocity.y, vel.linearVelocity.z);
}
else if (status & VRSTATUS_ORG)
{
Q_snprintf(cmd, sizeof(cmd), "%s %u %g %g %g %g %g %g\n", xr.actions[h].actname, status,
angles[0], angles[1], angles[2], org[0], org[1], org[2]);
}
else
{
Q_snprintf(cmd, sizeof(cmd), "%s %u %g %g %g\n", xr.actions[h].actname, status,
angles[0], angles[1], angles[2]);
}
cmdfuncs->AddText(cmd, false); cmdfuncs->AddText(cmd, false);
} }
} }
@ -1432,12 +1559,23 @@ static void XR_UpdateInputs(XrTime time)
XrActionStateGetInfo info = {XR_TYPE_ACTION_STATE_GET_INFO}; XrActionStateGetInfo info = {XR_TYPE_ACTION_STATE_GET_INFO};
info.action = xr.actions[h].action; info.action = xr.actions[h].action;
info.subactionPath = xr.actions[h].path; info.subactionPath = xr.actions[h].path;
xrGetActionStateBoolean(xr.session, &info, &state); if (XR_FAILED(xrGetActionStateBoolean(xr.session, &info, &state)))
break;
if (!state.isActive) state.currentState = XR_FALSE; if (!state.isActive) state.currentState = XR_FALSE;
if ((!!state.currentState) != xr.actions[h].held) if ((!!state.currentState) != xr.actions[h].held)
{ {
xr.actions[h].held = !!state.currentState; xr.actions[h].held = !!state.currentState;
if (xr.actions[h].held || *xr.actions[h].actname == '+') if (xr.actions[h].held && !activesets[xr.actions[h].set])
break; //don't fire the down event when this action's set isn't meant to be active...
if (*xr.actions[h].actname == '#')
{
int k = inputfuncs->GetKeyCode(xr.actions[h].actname+1, NULL);
if (k >= 0)
inputfuncs->KeyEvent(0, xr.actions[h].held, k, 0);
else
Con_Printf("Key \"%s\" for action \"%s\" is not a known key code\n", xr.actions[h].actname+1, xr.actions[h].actdescription);
}
else if (xr.actions[h].held || *xr.actions[h].actname == '+')
{ {
char cmd[256]; char cmd[256];
Q_strlcpy(cmd, xr.actions[h].actname, sizeof(cmd)); Q_strlcpy(cmd, xr.actions[h].actname, sizeof(cmd));
@ -1455,13 +1593,36 @@ static void XR_UpdateInputs(XrTime time)
XrActionStateGetInfo info = {XR_TYPE_ACTION_STATE_GET_INFO}; XrActionStateGetInfo info = {XR_TYPE_ACTION_STATE_GET_INFO};
info.action = xr.actions[h].action; info.action = xr.actions[h].action;
info.subactionPath = xr.actions[h].path; info.subactionPath = xr.actions[h].path;
xrGetActionStateFloat(xr.session, &info, &state); if (XR_FAILED(xrGetActionStateFloat(xr.session, &info, &state)))
break;
if (!state.isActive) state.currentState = 0.0f; if (state.isActive && activesets[xr.actions[h].set])
{ {
char cmd[256]; char cmd[256];
Q_snprintf(cmd, sizeof(cmd), "%s %g\n", xr.actions[h].actname, state.currentState); if (!strncasecmp(xr.actions[h].actname, "#GP_AXIS_", 9))
cmdfuncs->AddText(cmd, false); {
int axis = -1;
if (!strcasecmp(xr.actions[h].actname+9, "LTRIGGER"))
axis = GPAXIS_LT_TRIGGER;
else if (!strcasecmp(xr.actions[h].actname+9, "RTRIGGER"))
axis = GPAXIS_RT_TRIGGER;
else if (!strcasecmp(xr.actions[h].actname+9, "LEFT_X"))
axis = GPAXIS_LT_RIGHT;
else if (!strcasecmp(xr.actions[h].actname+9, "LEFT_Y"))
axis = GPAXIS_LT_DOWN;
else if (!strcasecmp(xr.actions[h].actname+9, "RIGHT_X"))
axis = GPAXIS_RT_RIGHT;
else if (!strcasecmp(xr.actions[h].actname+9, "RIGHT_Y"))
axis = GPAXIS_RT_DOWN;
else
Con_Printf("Unknown gamepad axis: \"%s\"\n", xr.actions[h].actname+1);
inputfuncs->JoystickAxisEvent(0, axis, state.currentState);
}
else
{
Q_snprintf(cmd, sizeof(cmd), "%s %g\n", xr.actions[h].actname, state.currentState);
cmdfuncs->AddText(cmd, false);
}
} }
} }
break; break;
@ -1471,9 +1632,10 @@ static void XR_UpdateInputs(XrTime time)
XrActionStateGetInfo info = {XR_TYPE_ACTION_STATE_GET_INFO}; XrActionStateGetInfo info = {XR_TYPE_ACTION_STATE_GET_INFO};
info.action = xr.actions[h].action; info.action = xr.actions[h].action;
info.subactionPath = xr.actions[h].path; info.subactionPath = xr.actions[h].path;
xrGetActionStateVector2f(xr.session, &info, &state); if (XR_FAILED(xrGetActionStateVector2f(xr.session, &info, &state)))
break;
if (!state.isActive) state.currentState.x = state.currentState.y = 0.0f; if (state.isActive && activesets[xr.actions[h].set])
{ {
char cmd[256]; char cmd[256];
Q_snprintf(cmd, sizeof(cmd), "%s %g %g\n", xr.actions[h].actname, state.currentState.x, state.currentState.y); Q_snprintf(cmd, sizeof(cmd), "%s %g %g\n", xr.actions[h].actname, state.currentState.x, state.currentState.y);
@ -1481,8 +1643,9 @@ static void XR_UpdateInputs(XrTime time)
} }
} }
break; break;
case XR_ACTION_TYPE_VIBRATION_OUTPUT: case XR_ACTION_TYPE_VIBRATION_OUTPUT: //output only, nothing to read.
default: case XR_ACTION_TYPE_MAX_ENUM: //not a real value
safedefault:
break; break;
} }
} }
@ -1497,6 +1660,7 @@ static qboolean XR_Begin(void)
xr.beginning = false; xr.beginning = false;
xr.ending = false; xr.ending = false;
xr.inputsdirty = true;
{ {
XrSessionCreateInfo sessioninfo = {XR_TYPE_SESSION_CREATE_INFO}; XrSessionCreateInfo sessioninfo = {XR_TYPE_SESSION_CREATE_INFO};
@ -1855,7 +2019,7 @@ static qboolean XR_SyncFrame(double *frametime)
{ {
if (time < xr.time) //make sure time doesn't go backward... if (time < xr.time) //make sure time doesn't go backward...
time = xr.time; time = xr.time;
*frametime = (time-xr.time)/1000000000.0; *frametime = NANOSECONDS_TO_SECONDS(time-xr.time);
} }
xr.time = time; xr.time = time;
xr.timeknown = true; xr.timeknown = true;
@ -1884,10 +2048,11 @@ static qboolean XR_Render(void(*rendereye)(texid_t tex, vec4_t fovoverride, vec3
if (!xr.session || !xr.beginning) if (!xr.session || !xr.beginning)
return false; return false;
if (xr.inputsdirty) if (xr.inputsdirty && xr.state==XR_SESSION_STATE_FOCUSED)
{ {
xr.inputsdirty = false; xr.inputsdirty = false;
XR_PrintInputs(); if (xr_debug->ival)
XR_PrintInputs();
} }
#ifdef XR_EXT_hand_tracking #ifdef XR_EXT_hand_tracking
for (u = 0; u < 2; u++) for (u = 0; u < 2; u++)
@ -2038,7 +2203,7 @@ static qboolean XR_Render(void(*rendereye)(texid_t tex, vec4_t fovoverride, vec3
fovoverride[2] = eyeview[u].fov.angleDown * (180/M_PI); fovoverride[2] = eyeview[u].fov.angleDown * (180/M_PI);
fovoverride[3] = eyeview[u].fov.angleUp * (180/M_PI); fovoverride[3] = eyeview[u].fov.angleUp * (180/M_PI);
waitinfo.timeout = 100000; waitinfo.timeout = SECONDS_TO_NANOSECONDS(0.1);
res = xrWaitSwapchainImage(xr.eye[u].swapchain, &waitinfo); res = xrWaitSwapchainImage(xr.eye[u].swapchain, &waitinfo);
if (XR_FAILED(res)) if (XR_FAILED(res))
Con_Printf("xrWaitSwapchainImage: %s\n", XR_StringForResult(res)); Con_Printf("xrWaitSwapchainImage: %s\n", XR_StringForResult(res));
@ -2108,6 +2273,7 @@ qboolean Plug_Init(void)
fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs)); fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs));
inputfuncs = plugfuncs->GetEngineInterface(pluginputfuncs_name, sizeof(*inputfuncs)); inputfuncs = plugfuncs->GetEngineInterface(pluginputfuncs_name, sizeof(*inputfuncs));
plugfuncs->ExportFunction("MayUnload", XR_PluginMayUnload); plugfuncs->ExportFunction("MayUnload", XR_PluginMayUnload);
plugfuncs->ExportFunction("ExecuteCommand", XR_HapticCommand_f);
if (plugfuncs->ExportInterface(plugvrfuncs_name, &openxr, sizeof(openxr))) if (plugfuncs->ExportInterface(plugvrfuncs_name, &openxr, sizeof(openxr)))
{ {
xr_enable = cvarfuncs->GetNVFDG("xr_enable", "1", 0, "Controls whether to use openxr rendering or not.", "OpenXR configuration"); xr_enable = cvarfuncs->GetNVFDG("xr_enable", "1", 0, "Controls whether to use openxr rendering or not.", "OpenXR configuration");

View file

@ -306,7 +306,13 @@ typedef struct //for menu-like stuff
F(const char*,GetKeyBind, (int bindmap, int keynum, int modifier)); F(const char*,GetKeyBind, (int bindmap, int keynum, int modifier));
F(void, SetKeyBind, (int bindmap, int keycode, int modifier, const char *newbinding)); F(void, SetKeyBind, (int bindmap, int keycode, int modifier, const char *newbinding));
F(qboolean, SetHandPosition, (const char *devname, vec3_t org, vec3_t ang, vec3_t vel, vec3_t avel)); //for VR. unsigned int (*GetKeyDest) (void);
void (*KeyEvent) (unsigned int devid, int down, int keycode, int unicode);
void (*MouseMove) (unsigned int devid, int abs, float x, float y, float z, float size);
void (*JoystickAxisEvent) (unsigned int devid, int axis, float value);
void (*Accelerometer) (unsigned int devid, float x, float y, float z);
void (*Gyroscope) (unsigned int devid, float pitch, float yaw, float roll);
qboolean (*SetHandPosition) (const char *devname, vec3_t org, vec3_t ang, vec3_t vel, vec3_t avel); //for VR.
#define pluginputfuncs_name "Input" #define pluginputfuncs_name "Input"
} pluginputfuncs_t; } pluginputfuncs_t;