WebVR controllers and Chromium’s Gamepad API

Stewart Smith
4 min readJun 14, 2017

--

TL;DR: Chromium’s Gamepad API can be a quirky thing. But if you’re building interactive WebVR experiences with Three.js then VRController.js is here to help.

UPDATE: Some time has passed since I first wrote this. If you’re looking for deeper insight into using VR hand controllers in WebXR check out the Space Rocks technical deep dive where you’ll find that and more:

I’ve been building virtual reality experiences with the WebVR API for some time now—primarily for the HTC Vive, but the beauty of making virtual reality for the Web is that it will also run on the Oculus Rift, Google Daydream, and so on with little additional effort. (It is the Web, after all!) In 2016 I wrote a Medium post about my first experience with virtual reality and my first “polished” WebVR experiment, Day & Night.

While working on Day & Night I contributed to Three.js’s ViveController class by adding simple button events. This tiny exercise inspired me to take things further by creating a generic VRController class that could support several controller models at once. (With many thanks to Boris Smus for allowing us to use his OrientationArmModel to better support 3DOF controllers.) I submitted a pull request to add VRController to Three.js back in March and in the meantime I’m more actively maintaining that generic VRController code here:

The backbone of VRController is the Gamepad API and over the course of reading the docs and experimenting with it I’ve refined my understanding of its occasional absurdities in the WebVR build of Chromium 😉 (Written in jest—I am a strong admirer of Brandon Jones and his efforts both with the Gamepad API and WebVR API, as well as the efforts of his colleagues and counterparts elsewhere.) Here are some relevant observations that you can follow along with in your JavaScript console—provided you have a VR rig attached and you’re using the WebVR build of Chromium. (If you’re one of the handful of folks making WebVR right now there’s a good chance you have this setup in place already.)

  1. With both of your VR controllers turned off, open a fresh browser window. You don’t need to load any particular website in it because our experimenting will take place right in the JavaScript console. So fire up the console and type navigator.getGamepads(). It will return an Array with four empty slots. We’re off to a good start.
  2. Power on a single controller but do not touch any of the buttons or trackpads. navigator.getGamepads() will still return an Array with four empty slots. This is a privacy protection feature: The Gamepad API requires a user interaction before it will allow JavaScript to know that the gamepad exists. Does your UX reflect this? You can’t just tell your user to power up their controller—you have to let them know they need to hit a button too. If you don’t communicate this properly they will just think your junk is borked. And at that point it effectively is.
  3. Touch any button on your powered-on controller. Bam! Now the controller exists. But wait… Yes, the controller you turned on exists but the controller you did not turn on also exists! Ghost controller! You’ll notice navigator.getGamepads()is now returning an Array with Gamepad instances in the first two slots, followed by two empty slots. Both of these instances report connected: true even though one is not even powered on. Madness! (And how does it know there’s supposed to be a second controller? Is that from using two controllers with this browser previously?) If you inspect the pose object of each you’ll see that the controller you turned on has real Float32Arrays for position and orientation while the other controller’s position and orientation are set to null. At least that part makes sense.
  4. Now turn off the controller you powered on. The Gamepad API will still report that it exists! So now you have two ghost controllers and both instances deceptively claim connected: true.
  5. Keep both controllers powered off. Hard refresh the page. navigator.getGamepads() still reports those controllers are connected!
  6. So how do you restore sanity? Leave your current browser window open while opening a fresh one. In the new window navigator.getGamepads() reports four empty slots. Phew! Normalcy again! (But of course the old browser window still reports two gamepads are connected.) At this point your new window mirrors Step 1 above and you can run the whole cycle over again.

To wrangle this Gamepad API behavior into something more intuitive, VRController only accepts a gamepad as “existing”—and thus fires a connection event—when its pose object contains either a valid position or valid orientation object. And if at any point both of these become null again (ie. the gamepad loses tracking) VRController will fire a disconnection event. Perhaps it’s not the most elegant solution but over the course of some experimentation and iteration it does seem the most useful for handling controllers across Chromium, Firefox, and beyond.

If you’re building virtual reality websites with Three.js give VRController a spin and leave your feedback either here or on GitHub. And if you have more insight into using the Gamepad API with VR controllers in general do contribute!

--

--

Stewart Smith

I’m Stewart Smith—a creative polymath in Brooklyn NY. I’m excited about spatial computing, quantum computing, machine learning, & more. https://stewartsmith.io