SteamVR HMD worn state not set when user starts with HMD worn

Description:

When a user launches a game with the headset already worn, the current steamvr plugin code will never set the WornState to EHMDWornState::Worn.

Details:

The problem is the engine is only using the polling method:

while (VRSystem->PollNextEvent(&VREvent, sizeof(VREvent)))

And checking for the interaction started event:

    case vr::VREvent_TrackedDeviceUserInteractionStarted:
        // if the event was sent by the HMD
        if (VREvent.trackedDeviceIndex == vr::k_unTrackedDeviceIndex_Hmd)
        {
            // Save the position we are currently at and put us in the state where we could move to a worn state
            bShouldCheckHMDPosition = true;
            HMDStartLocation = Position;
        }
        break;

However, this isn’t correct, because if the user begins the game already wearing the headset, VREvent_TrackedDeviceUserInteractionStarted will never fire. This also can happen if they begin the game in certain idle states.

From the OpenVR documentation:

/** Level of Hmd activity */
// UserInteraction_Timeout means the device is in the process of timing out.
// InUse = ( k_EDeviceActivityLevel_UserInteraction || k_EDeviceActivityLevel_UserInteraction_Timeout )
// VREvent_TrackedDeviceUserInteractionStarted fires when the devices transitions from Standby -> UserInteraction or Idle -> UserInteraction.
// VREvent_TrackedDeviceUserInteractionEnded fires when the devices transitions from UserInteraction_Timeout -> Idle

The engine should do an initial call to:

/** Returns the level of activity on the device. */
virtual EDeviceActivityLevel GetTrackedDeviceActivityLevel( vr::TrackedDeviceIndex_t unDeviceId ) = 0;

to determine if the HMD is worn, and then set the worn state appropriately. After an initial call to that it should be safe to stick to the event polling method for future transitions.

2 Likes

Note that the initial call to GetTrackedDeviceActivityLevel seems to return k_EDeviceActivityLevel_UserInteraction_Timeout if the HMD starts while not worn, even if it hasn’t been worn for several seconds (I expected it to be in Standby or Idle, but it isn’t).

There is a guarantee of an event transitioning from k_EDeviceActivityLevel_UserInteraction_Timeout → k_EDeviceActivityLevel_UserInteraction_Idle, but no event when transitioning from k_EDeviceActivityLevel_UserInteraction_Timeout → k_EDeviceActivityLevel_UserInteraction.

The solution seems to be to assume unworn if started in k_EDeviceActivityLevel_UserInteraction_Timeout or k_EDeviceActivityLevel_UserInteraction_Unknown, then keep checking until you get something other than those two. Once that happens, from then on the normal transition events should work but you will need to handle the first transition into k_EDeviceActivityLevel_UserInteraction and treat it as “worn” in case it happens before a transition into k_EDeviceActivityLevel_Standby or k_EDeviceActivityLevel_Idle.

To make things more responsive you can also query the proximity sensor on Vive (some of these events are based on it, but they also measure movement). However there is a bug report that Oculus will report it has a proximity sensor but not provide proximity sensor status ( Access to Vive proximity sensor :: SteamVR Developer Hardware General Discussions ), so I am avoiding that method for now (Edit: I tested and it works fine on Oculus).

2 Likes