How to hook Three.js VR mode into a custom animation loop

When rendering non-VR scenes, I’ve been using Three.js in my own animation loop, using a custom loop manager. For example, I might write something like

import AnimationLoop from 'animation-loop'

// ... make a Three `scene`, `camera`, `renderer`, etc ...

const loop = new AnimationLoop
loop.start()

loop.addAnimationFn(( elapsedTime, deltaTime ) => {
  renderer.render( scene, camera )
})

setTimeout(() => loop.pause(), 5000)

Now I’m looking into VR/AR/XR modes of Three.js, and it seems it interferes with my ability to decouple my rendering loop from the Three engine, because it requires renderer.setAnimationLoop.

Is there a way to render a Three VR scene using my own animation loop so that I can use the existing infrastructure that I have?

A little more background: I’m working on https://github.com/trusktr/infamous, which abstracts away the animation loop. With infamous you can take an <i-node> HTML element and give it rotation, position, etc,

<i-scene>
  <i-node rotation="0 30 0">
  </i-node>
</i-scene>

Example

Behind the scenes, any time the node rotation attribute is updated, it does something like

loop.addAnimationFn(( elapsedTime, deltaTime ) => {
  // recalculate world transform of the modified node
})

The bottom line is, I’m manually calling requestAnimationFrame behind the scenes, and even more importantly, when there are no updates being performed on any node attributes, there are no requestAnimationFrame calls and the CPU idles at 0% use. Also, I have pause/resume functionality for long animations, etc.

I’m new to WebVR/XR, so I’m curious to know what the pattern is for using Three.js VR-mode in a custom loop.

Have you looked into how the WebGLRendererer.startAnimationLoop is implemented? Basing your custom loop on that might be a good start.

Yeah, I saw setAnimationLoop, and I am trying to come up with something.

At the moment, the hack looks like this:


// the default value is `window.requestAnimationFrame.bind( window )`
loop.requestFrame = renderer.setAnimationLoop.bind( renderer )

// then loop uses this.requestFrame internally, which is either window.requestAnimationFrame or renderer.setAnimationLoop
loop.addAnimationFn(( elapsedTime, deltaTime ) => {
  // recalculate world transform of the modified node, re-render the scene
})

I realize that setAnimationLoop is being called too many times when used in place of requestAnimationFrame, but at least it works. The loop instance it doesn’t know the difference, it uses requestFrame exactly the same as one uses requestAnimationFrame.

However, due to the way setAnimationLoop works, features like loop.pause() or loop.stop() don’t work because Three.js keeps ticking the function passed to setAnimationLoop, but at least this makes it tick.

The main point is, I don’t want to be coupled to Three’s loop mechanism, I want to make my own, then I want to be able to render anything (including Babylon scenes, PlayCanvas scenes, or anything else) and I want to be the one in control of the animation loop just like I am when using native requestAnimationFrame.