Transform XR rotation using controller joystick

I’m wondering if there is a way for the VR controller joystick to transform the rotation of the reference space. So there is nothing intersection. The joystick is looking around instead of a headturn ? Is there rotation cordinates given from the joystick move event ? So I can dynamically transform rotation a set amount left , right, up and down ? I would have to update the rotation similar to the teleport. I already update the reference space from the positions and rotations of the non-XR camera.

 const offsetPosition = { x: - INTERSECTION.x, y: - INTERSECTION.y, z: - INTERSECTION.z, w: 1 };
					const offsetRotation = new THREE.Quaternion();
					const transform = new XRRigidTransform( offsetPosition, offsetRotation );
					const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace( transform );

					renderer.xr.setReferenceSpace( teleportSpaceOffset );

Here’s some code from an old Angular project I had. It uses observables, but you can skip this. This is TypeScript, so you can just remove the type information. Sorry, I don’t have just a vanilla JavaScript version.

Basically, you get access to the gamepad in the connect event. In animation frame, you get current axis and deltaTime information


animate() {
        this.joystick_event.axis.x = this.gamepad.axes[2];
        this.joystick_event.axis.y = this.gamepad.axes[3];
        this.joystick_event.deltaTime = event.state.delta;
        this.joystickaxis.next(this.joystick_event);

        // ... render scene, etc
}

Then you calculate a new position and finally teleport (like you have)


    this.xr.joystickaxis.subscribe((event: AxisEvent) => {
        this.move(event);
        this.teleport();
    });

  teleport() {
    const offsetPosition = <DOMPointReadOnly>{ x: -this.position.x, y: 0, z: -this.position.z, w: 1 };
    const offsetRotation = <DOMPointReadOnly>{ x: 0, y: 0, z: 0, w: 1 };
    const transform = new XRRigidTransform(offsetPosition, offsetRotation);
    if (this.baseReferenceSpace) {
      const teleportSpaceOffset = this.baseReferenceSpace.getOffsetReferenceSpace(transform);
      this.manager.setReferenceSpace(teleportSpaceOffset);
    }
  }

  private getForwardVector(): Vector3 {
    const playerDirection = new Vector3()

    this.camera.getWorldDirection(playerDirection);
    playerDirection.y = 0;
    playerDirection.normalize();

    return playerDirection;
  }

  private playerVelocity = new Vector3();

  private move(event: AxisEvent) {
    if (event.axis.x == 0 && event.axis.y == 0) return;

    const deltaTime = event.deltaTime
    const speedDelta = deltaTime * event.axis.y * this.speed;
    this.playerVelocity.add(this.getForwardVector().multiplyScalar(speedDelta));

    let damping = Math.exp(-3 * deltaTime) - 1;

    this.playerVelocity.addScaledVector(this.playerVelocity, damping);

    const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime);
    this.position.sub(deltaPosition)

  }

It was tricky to get right

Here’s a live demo showing how it works. Click on these panels to give it a try
image

Hope this helps

1 Like

You are such a legend. So I use the move event which works with the joystick on the controller but I get the axis from the gamepad property. I might need to transform on rotation not position. It’s just for immersive video for my purpose. I’ll come up with something and report back.

It’s basically orbitcontrols but in the headset to rotate. This is my code when launching XR to transform to the current camera position. The tilt is for when lying down flat.

onSessionStart(ev, event) {


  
    this.currentControls.update();
 
    const xrManager = this.renderer.xr,
    camera = this.camera,
    baseReferenceSpace = xrManager.getReferenceSpace(),
    offsetPosition = camera.position,
    offsetRotation = camera.rotation;

    const transform = new XRRigidTransform( offsetPosition, { x: this.config.xrTiltOffset ? offsetRotation.x : 0, y: -(offsetRotation.y - this.config.xrPanOffset), z: offsetRotation.z, w: offsetRotation.w } ),
    //const transform = new XRRigidTransform( offsetPosition, { x: offsetRotation.x, y: -(offsetRotation.y - 0.5) , z: offsetRotation.z, w: offsetRotation.w } ),
    teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace( transform );

    xrManager.setReferenceSpace( teleportSpaceOffset );
 
    this.startVRUIRendering();
    this.fullScreenResize(true);
    this.onVREvent(ev, event);

    //force playback for visionOS
    if (this.isIpad) {
       // this.video.play();

        const handlePaused = () => {
            this.video.removeEventListener("pause", handlePaused);
            this.video.play();
        };

        this.video.addEventListener("pause", handlePaused);


    }
    
}

I have something like this that seems to affect rotation. I need to keep working on it.

controller1.addEventListener( 'move', () => {

					//console.log(controls1, controller1);

					if (controls1.gamepad.axes[3] !== 0) {
						console.log(controls1.gamepad.axes);

						let axisX = controls1.gamepad.axes[2],
						axisY = controls1.gamepad.axes[3];



						const offsetPosition = { x: 0, y:0, z: 0, w: 1 };
						const offsetRotation = new THREE.Quaternion();
						offsetRotation.x = axisX;
						offsetRotation.y = axisY;
						const transform = new XRRigidTransform( offsetPosition, offsetRotation );
						const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace( transform );

						renderer.xr.setReferenceSpace( teleportSpaceOffset );



					}

					

				});

In an actual headset the rotation left and right does transform a little bit but stops. Doesn’t rotate the whole way around. So I suppose I need to do something else.

Try looking at the OrbitControls. It might provide a clue on how to correctly implement rotation

I’ve tried to have a look there doesn’t seem to be anything out there in related to gamepad axes and orientation transform. Which is unrelated to the camera. I tried a few things similar to the pointerlockcontrols. Will keep trying.

After trial and error I have this rotating correctly left and right. But up and down it rotates wierdly still. Might need more improvements.

const _euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
let rotateSpeed = 4.0;
const _PI_2 = Math.PI / 2;
let minPolarAngle = 0; // radians
let maxPolarAngle = Math.PI; // radians

const offsetRotation = new THREE.Quaternion();


controller1.addEventListener( 'move', () => {


if (controls1.gamepad.axes[3] !== 0) {
	console.log(controls1.gamepad.axes);

	let axisX = controls1.gamepad.axes[2],
	axisY = controls1.gamepad.axes[3];

	_euler.setFromQuaternion( offsetRotation );

	_euler.y -= axisX * 0.002 * rotateSpeed;
	_euler.x -= axisY * 0.002 * rotateSpeed;

	_euler.x = Math.max( _PI_2 - maxPolarAngle, Math.min( _PI_2 - minPolarAngle, _euler.x ) );

	offsetRotation.setFromEuler( _euler );


	const offsetPosition = { x: 0, y:0, z: 0, w: 1 };
	const transform = new XRRigidTransform( offsetPosition, offsetRotation );
	const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace( transform );

	renderer.xr.setReferenceSpace( teleportSpaceOffset );



}



});