VR Camera is "tilted" after setTarget()

I’m working on my little art gallery, and one of the features is that you can click on a piece of art hanging on the wall and it will teleport you directly in front of it.

This works fine on the browser, but on my VR headset (Oculus Go), I’m seeing something weird. If my head is tilted at all when I click on some art, it teleports correctly, but the camera is now tilted in the direction my head was tilted at the time. This leads quickly to the very disconcerting, funhouse-style experience of a tilted floor.

Here’s a minimal reproduction of the issue:

Click on the grey “painting” to teleport. Do so several times, each time tilting your head before you click.

Is anyone else seeing this issue in their VR devices? Is there some sort of transform I need to do to setTarget to account for the VR head pose? Or some additional call I need to make to restore the pitch/yaw of the camera to where it was before?

I think what is going on is that I somehow need to reposition the direction the camera “player” is “facing” without just setting the target to the artwork… at least in immersive mode.

In other words, I need to modify the “yaw” rotation (“y”, which wall the camera is facing), without modifying the “pitch” (“x”, facing up or down) or the “roll” (“z”, head tilt). At least I think I got those terms right?

Ideally, I would like to adjust the “y” rotation so the user’s body is facing toward the art rather than re-centering their head’s left-right “look” direction. But I don’t think that information would be available for a simpler VR device. So I suppose I’ll have to live with the idea that if you look left or right and click something, you’ll have to rotate your body to not look at the art from your new position at that same awkward angle.

Pinging @RaananW who will be able to have a look when he comes back from vacation.

I was able to improve the situation by just setting activeCamera.rotation.y to match the photo’s orientation rather than using setTarget.

In-browser, this works great. But in VR, setting rotation has no effect (on any axis).

I’ve also been playing around with Quaternion.RotationYawPitchRoll but it seems to have the same issue as setTarget… can’t seem to find the magic values for “x” and “z” so only the “y” rotation ends up being changed.

Hi @richardtallent,

yes - using setTarget on the XR camera is not very advisable. Unless you want to have your users enjoy a very weird experience :slight_smile:
As you said, what you need to do is set the rotation on the Y axis only. But as you mentioned - in XR it is actually expected that the user will rotate towards something and not that the “thing” rotates to the user. Having said that - the teleportation feature does have a landing-rotation behavior that you might find helpful in this case.
If you have the expected rotation, you can export the “y” component of it and multiply this Y-only quaternion with the camera’s current rotation. this will rotate the user to the point you want them to see. Roughly this way - Babylon.js/WebXRControllerTeleportation.ts at master · BabylonJS/Babylon.js (github.com)

Thanks! I figured out something that works, but given the number of conversions and the math I’m doing here, it feels a little “hackish.”

Is there a cleaner way to do this, i.e., making better use of the Babylon API?

function setYaw(targetDegrees: number, c: targetCamera) {
  const delta = constrainAngle(
    constrainAngle(targetDegrees)
    - Tools.ToDegrees(c.rotationQuaternion.toEulerAngles().y)
  )
  c.rotationQuaternion.multiplyInPlace(
    Quaternion.FromEulerAngles(0, Tools.ToRadians(delta), 0)
  )
}

function constrainAngle(x: number) {
	x -= 360 * Math.floor(x / 360)
	return x
}

As the camera doesn’t have .rotate , this will be the way to go.
I would recommend to not create constantly new instances (toEurlerAngels and FromEulerAngles both create new objects). And also - why not work in radians from the beginning? you are converting everything to degrees, and then converting it to radians. In your case you can use radians alone, no need to convert them.

Going all-radians sounds possible, I’d just need to rewrite the constrainAngle function and do a single conversion of the incoming targetYaw (which is already in degrees because my brain doesn’t work in radians).

However, the Eurler Angle conversions appear to be necessary, unless I’m missing something – I don’t see a way to get/set the yaw directly from the rotationQuaternion properties without conversion, and my (very, very fuzzy) understanding of Quaternions is that they aren’t operating in the same dimensional space (the x, y, and z components on them certainly don’t seem to correspond directly).

You are totally right. It is required to extract the “y” component from the quaternion, and it is not as simple as taking quaternion.y . The simplest way would be - convert to euler and use this radian value, just as you do.

1 Like