Pose action space can not filter between left and right hand

Simple problem and it is not working: Get the pose of the left and right hand controller. According to spec you should create an action like this:

XrActionCreateInfo createInfo{XR_TYPE_ACTION_CREATE_INFO};
strcpy(createInfo.actionName, name);
strcpy(createInfo.localizedActionName, localizedName);
createInfo.actionType = XR_ACTION_TYPE_POSE_INPUT;
const XrPath subactionPaths[ 2 ] = {path("/user/hand/left"), path("/user/hand/right"};
createInfo.subactionPaths = subactionPaths;
createInfo.countSubactionPaths = 2;
xrCreateAction(actionSet, &createInfo, &action);

Which allows filtering for left and right hand. Then I suggest this action for “/user/hand/left/input/aim/pose” and “/user/hand/right/input/aim/pose”.

Now according to spec I need an action space like this (one for each hand):

XrActionSpaceCreateInfo createInfo{XR_TYPE_ACTION_SPACE_CREATE_INFO};
createInfo.action = action;
createInfo.subactionPath = path("/user/hand/left"); // respectively path("/user/hand/right" for the other hand
createInfo.poseInActionSpace.orientation.w = 1.0f;
xrCreateActionSpace(session, &createInfo, &actionSpaceLeft)); // respectively actionSpaceRight

So far so good. Then I should use locate space to get the poses:

XrSpaceVelocity velocity{XR_TYPE_SPACE_VELOCITY};
XrSpaceLocation location{XR_TYPE_SPACE_LOCATION};
location.pose.orientation.w = 1.0f;
location.next = &velocity;
if(XR_SUCCEEDED(xrLocateSpace(actionSpaceLeft, stageSpace, predictedDisplayTime, &location))){
   // store left position...
}
if(XR_SUCCEEDED(xrLocateSpace(actionSpaceRight, stageSpace, predictedDisplayTime, &location))){
   // store left position...
}

Now my problem is this. I start out with no controller attached. I attach the right controller. I get the pose for the right controller. Now I attach the left controller. I get now the pose of the left controller for both controllers with the right controller being totally ignored. This also happens if I start with both attached or attaching them the other way round. It looks as if the filtering is ignored and the first one (left hand controller) always picked.

Do I understand something wrong on the spec or is it not possible with OpenXR to get the pose of the hand controllers independently without using different actions? If so how are you then supposed to deal with an undefined number of trackers which have undefined (according to spec) unique path names?

Do I understand something wrong on the spec or is it not possible with OpenXR to get the pose of the hand controllers independently without using different actions?

It is sorta possible to xrSyncActions and then get the action state for each subaction path, but that’s not really the idea.

When binding multiple pose inputs to one action, the runtime will choose one pose: The OpenXR Specification

Edit: Shouldn’t reply when tired, what I referenced only applied when you query action states with XR_NULL_PATH, i.e. intentionally opting out of filtering.
Yes, you can create one action with both /user/hand/left and /user/hand/right paths and then call xrGetActionStatePose twice with /user/hand/left, /user/hand/right. What you do need is separate spaces. That’s what I do in my own example too. Though the vive tracker point still stands as you have to create an action with all of the subaction paths that you want to query it with, and unlike the hand paths there is no way at all to know the (opaque) persistent paths of trackers. Even the runtime might have never seen the tracker before.

For Vive trackers that you want to track with the persistent path without a role, that is a good question. For trackers that are active at startup, the trackers can be enumerated with xrEnumerateViveTrackerPathsHTCX and actions created before the session is started and the action set is attached to the session and becomes immutable.

For vive trackers that are powered on during application runtime: ???

Destroying the current session and creating a new one with a new set of actions is rather heavyweight but what else can you do…

This behavior sounds like a runtime bug. The code snippet you showed above looks ok. You can also try the HelloXR sample app from Khronos to see if you observe similar problem. The code seems equivalent to yours.

OpenXR-SDK-Source/openxr_program.cpp at master · KhronosGroup/OpenXR-SDK-Source (github.com)

About the trackers I ran into a wall there. My problem is that the path components written in the specification are all rejected as invalid path. I’ve tried using the persistent path the enumerate call returns as base path together with “/input/grip/pose” but it’s not working. Hence I got path like “/devices/htc/vive_trackerlhr-1c4039a2/input/grip/pose” but they are rejected. Anybody got trackers to work? I’m just interested in the device pose.

I have tracker poses working with SteamVR beta version 1.21.8, although there are still a lot of bugs in the runtime. In my tests, poses from all roles except handheld_object should work. I haven’t tested if using the persistent path to get the poses works though.

My problem is that I can not use roles. The user has to leave all trackers unassigned since the use of trackers is dynamic.

I tried to test with the hellovr but it renders nothing in the Hmd so I can not verify what controller poses are tracked or not. I also tried to use the validation layer but it seems SteamVR does not honor layers set during XrCreateInstance. The validation library is loaded and put in the layer list but SteamVR does not call the layer creation function. So I’m getting nowhere here.

Would it be allowed from the specificiation side to use a path like “/user/vive_tracker_htcx/role” with individual actions?

Would be really great if there would be a way to enumerate path supported on a device. Otherwise it’s guessing around since the path in the spec is not respected by SteamVR… at least not on vive trackers :frowning:

I don’t think the spec allows that, but it seems to enforce being able to doing it using the persistent ID at least.

It might be a good idea to report the issue to Valve as well if you haven’t already. They seem to still be actively working on the their tracker implementation.

My experience with their “support” and “bug tracking efforts” had been very… abysmal. Maybe you know a more “direct” path which actually does care about bugs?

Unfortunately not. I think the only option really is to report the issue to the bug tracker or discussion boards and wait a few months or years for them to fix it. The developers likely do read the issues but will almost never reply.

I tested with Windows and it shows the same problem.

Have you tested the null path too? The specification lists the allowed values for “ROLE_VALUE” which are like “chest”, “left_foot” and stuff like this. Interestingly it contains also the value “XR_NULL_PATH” which is logic since a tracker can have no role assigned. This value though leads to an invalid path since according to spec path requires all lower case. I tried “…/role/xr_null_path/…” but it got rejected. Also tried “…/role/input/…” but it got rejected. Other values from ROLE_VALUE are accepted though they won’t work since the trackers have no role assigned. I’m curious to know how you are supposed to use the XR_NULL_VALUE in a path.

I don’t think I tested it, since it wouldn’t point to any specific tracker. Not sure why they would list it there.

The xrEnumerateViveTrackerPathsHTCX returns XR_NULL_PATH for trackers without a role so I guess it makes sense to add a value for those too otherwise you can not access them if the persistent path is not working. That said I find it annoying it does not. I consider it an important use case to have all trackers not assigned a role.

yeah that’s just a quirk of the phrasing. you’re not supposed to use XR_NULL_PATH as a role, it means trackers can have no role.

I’m in contact right now with Kyle Chen from HTC which is one of the original authors of the specification. The null value is indeed a bit unclearly specified. According to him SteamVR does not allow trackers without a role. They are shown as “disabled” in the SteamVR UI. But in my case the trackers are assigned a role: held in hand. So SteamVR returns null role path albeit a role is assigned. I have the impression something is gravely broken deep down in SteamVR but the discussion is still ongoing.

Yeah, that role specifically is broken in my testing as well.

I changed it to a different role but the result had been the same. SteamVR is spaming connect/disconnect messages at high rate per frame update and does not seem to make up it’s mind if the tracker is now connected or not.

It looks like my communication lane with HTC ended. It’s down to SteamVR problem it looks like.

@Rectus can you verify something for me? If you assign a role to the tracker in the SteamUI does enumeration and the connected event report a valid role path or XR_NULL_PATH? Because with me trackers with assigned role return XR_NULL_PATH and I am spammed with tracker connected messages with the trackers fanatically switching between connected/disconnected state. Trying to gain a stab at this problem.

Testing with 1.21.8 under Windows, both enumerating the trackers and the XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX event return correct persistent and role paths when initially connected.

The runtime does not send any events on changing the role or disconnecting the tracker though. It seems seems to always consider the tracker connected once it is detected by the current SteamVR session. Checking the current interaction profile with xrGetCurrentInteractionProfile() always returns the tracker profile as being active even after the tracker has changed role or disconnected.

I did some more testing. When the tracker is assigned held-in-hand I get the wrong role reported and the message spamming. When I assign it for example chest it stops these behaviors. But if I assign a second tracker to this role I can not receive pose anymore. But this is what happens in reality. MoCap uses more trackers than can be mapped in SteamVR UI. How is that supposed to work?