This week I decided to take the WebVR/WebXR extension work I did for Van Wijnen to the next level: mainly to integrate metric information in some way into the scene, whether as 3D text objects or via some kind of Heads-Up Display (HUD). But before embarking on this I decided to take another look at the implementation of the HTML client.
When I first wrote the HTML page that consumes geometry data from the Refinery server, I decided to use raw JavaScript and WebVR/WebXR calls with three.js. I’d thought briefly about using A-Frame – an awesome framework started by Mozilla for creating VR experiences – but as the main basis of A-Frame is to “declare” content, I figured it didn’t really make sense. I was a little jealous of the capabilities built into A-Frame and contributed by its growing community, though, as I proceeded with the project. For instance, there’s some really nice controller support – including a component to help integrate teleportation – which I ended up having to replicate on my own, at least to some degree.
Anyway, before integrating some kind of text-based display of data into the VR scene, I decided to look again at A-Frame. My “Aha!” moment (although it was more of a “Doh!” moment, truth be told) came when I realised you could simply access the three.js scene object declared via A-Frame from JavaScript, and then use that to add low(er)-level three.js primitives, such as the one being sent across by the Refinery server’s geometry API. Which meant I could use the same basic technique as with the previous WebVR page but this time using A-Frame content as a foundation for the page. And get to integrate the various fun widgets people have been building to work with A-Frame.
Let’s take a quick look at the parabolic navigation with a simple camera-fixed HUD showing a few metrics from the Generative Design process. Here’s a static image, to give some sense of the approximate quality:
Here’s the navigation in action:
As we can no longer point at the sky (at least not in the same way as with a linear pointer) I’ve wired the day/night toggle to the controller’s trigger, instead.
The implementation tested well with the Gear VR, too, despite reports that the parabolic navigation can have performance issues. The page loads well when you don’t have a VR device connected, too, of course.
One interesting quirk related to the way the page loads geometry: because it’s loaded dynamically from the Refinery server, it gets created after the declarative content. Which would be fine, except for the geometry that’s displayed for the teleport destination (by default a torus with an open ended cylinder using an interesting texture). Because this geometry gets shown using a material with depthTest set to false, it doesn’t display above newer objects (this is just a quirk of how three.js renders a scene). To get around this issue, I found I could change the height of the displayed cylinder very slightly, which caused the geometry to be recreated. This needs to be done whenever we load a new scene, otherwise you just see a parabola without anything at the end. Interesting!
So far I’ve enjoyed using A-Frame for this project: it’s a good way to reduce the amount of code you need to maintain for your WebVR project. Hopefully this will also help with the transition to WebXR, too, in due course.