Three.js camera rotation order

5.4k views Asked by At

I'm trying to use a gamepad to rotate a camera in Three.js, using first-person-shooter style controls.

The browser detects the gamepad and recognises it's input, but the camera's order of rotation is wrong. When I rotate on the camera's local Y axis, it takes into account the local X rotation too, which is unwanted.

It seems I'm having the same problem as this guy, but his issue was solved using Three.js r54 and I'm using r60. He set camera.eulerOrder = "YXZ"; to get it working, but the current equivalent camera.rotation.order = "YXZ"; doesn't seem to work for me.

I'm aware of Three.js' built-in "FirstPersonControls" class, but it's not suitable for me as it doesn't accept controller input and it would be messy to shoehorn other non-movement controls in there later. I'm also aware of gamepad.js and have no interest in using it.

Can anyone help?

My rotation code:

function pollGamepad()
{
    gamepad = navigator.webkitGetGamepads()[0];

    //Rotation
    if(gamepad.axes[3] > 0.20)
    {
        camera.rotateX(-gamepad.axes[3] * 0.02);
    }
    if(gamepad.axes[3] < -0.20)
    {
        camera.rotateX(-gamepad.axes[3] * 0.02);
    }

    if(gamepad.axes[2] < -0.20)
    {
        camera.rotateY(-gamepad.axes[2] * 0.02);
    }
    if(gamepad.axes[2] > 0.20)
    {
        camera.rotateY(-gamepad.axes[2] * 0.02);
    }
}
3

There are 3 answers

4
IceCreamYou On BEST ANSWER

The approach that PointerLockControls uses is to create a hierarchy of objects: yawObject contains pitchObject contains camera. Then horizontal mouse (or joystick) movement would change the Y-rotation of the yaw object, vertical mouse (or joystick) movement would change the X-rotation of the pitch object, and the camera's rotation would stay fixed at the default (0, 0, -1). This just manually simulates Euler YXZ ordering but it may work better for you. It does create some awkwardness if you need to get the overall rotation.

In a custom controller I wrote recently, I achieved the same result by add()ing the camera to a single parent object and setting the parent object's Euler order to YXZ. I don't remember why this worked better than setting it on the camera directly, but it did.

0
Gonzalo On

I faced this problem myself and I came up with a simple solution. Always reset camera's x rotation to zero before performing a y rotation. Then, restore x rotation. So:

// Rotate camera Fps style
function rotateCamera(dx,dy){
    //store previous x rotation
    var x = camera.rotation.x;

    //reset camera's x rotation.
    camera.rotateX(-x);

    //rotate camera on y axis
    camera.rotateY(dy);

    //check if we are trying to look to high or too low
    if ( Math.abs( dx + x ) > Math.Pi/2 - 0.05) {
        camera.rotateX(x);
    else
        camera.rotateX(x+dx);

    //reset z rotation. Floating point operations might change z rotation during the above operations.
    camera.rotation.z = 0;
}
0
nale On
I pressed every button until it worked this is the code it pops out

    var controlers = [];
controlers[0] = 0;
controlers[1] = 0;
controlers[2] = 0;
controlers[3] = 0;
controlers[4] = 0;
controlers[5] = 0;
controlers[6] = 0;
controlers[7] = 0;
controlers[8] = 0;
controlers[20] = 0;
var shooting = [];
shooting[0] = 0;        
let controllerIndex = [];

window.addEventListener("gamepadconnected", (event) => {
  const gamepad = event.gamepad;
  controllerIndex[event.gamepad.index] = event.gamepad.index;
  console.log("connected");
});

window.addEventListener("gamepaddisconnected", (event) => {
  controllerIndex = [];
  console.log("disconnected");
});

function handleButtons(buttons, indexid) {
  for (let i = 0; i < buttons.length; i++) {
    var button = buttons[i];
    var buttonElement = document.getElementById(`controller-b${i}`);
    var selectedButtonClass = "selected-button";

    if (buttonElement) {
      if (button.value > 0) {
        buttonElement.classList.add(selectedButtonClass);
        buttonElement.style.filter = `contrast(${button.value * 150}%)`;
     

 } else {
        buttonElement.classList.remove(selectedButtonClass);
        buttonElement.style.filter = `contrast(100%)`;
      }
    }
  }
}

function updateStickk(elementId, leftRightAxis, upDownAxis, indexid) 
{
    const stickLeftRighta = leftRightAxis;
    const stickUpDowna = upDownAxis;

    var stickUpDown_b = ( (stickLeftRighta) * 100);

    var stickLeftRight_b = ( (stickUpDowna) * 100);



    if( stickLeftRight_b > 30  ){

    camera.translateZ(15);


    }
    if( stickLeftRight_b < -30  ){
    camera.translateZ(-15);
    }


    if( stickUpDown_b > 30  ){
    camera.translateX(15);
    }
    if( stickUpDown_b < -30  ){
    camera.translateX(-15);
    }
}

function updateStick(elementId, leftRightAxis, upDownAxis, indexid) 
{
   if( indexid != null)  
   {
           const multiplier = 25; 
           const stickLeftRight = leftRightAxis;
           const stickUpDown = upDownAxis;



     if( controlers[0] + 100 < Date.now())
     {

    camera.cameras[indexid] = camera.cameras[indexid];
    camera.target = new THREE.Vector3( 0, 0, 0  );

    var stickLeftRight_b = ( (stickLeftRight) * 100);
    var stickUpDown_b = ( (stickUpDown) * 100);

    if( stickLeftRight_b <= -100 || stickLeftRight_b >= 100 || stickUpDown_b < -10 || stickUpDown_b > 10 ){ 
    


        if(  stickUpDown_b < -10 )
        {

            if(camera.rotation.x > -25)
                    {
                        camera.rotateX(0.1) ; 
                    } 
                else 
                    {
                        camera.rotation.x = 0;
                    }

        
        }

    if( stickUpDown_b > 10  )
        {
            if(camera.rotation.x < 25)
            {
                camera.rotateX(-0.1) ; 
            } 
            else 
            {
                camera.rotation.x =  0;
            }
            
        }

        if(  stickLeftRight_b >= 100 )
        {
            if( camera.rotation.y >= -359)
            {
                camera.rotation.y -=  0.1 ;
                camera.rotation.z = 0;
                camera.rotation.x = 0 ;
            }
        }

        if( stickLeftRight_b <= -100 )
        {
            if( camera.rotation.y <= 359)
            {
                camera.rotation.y += 0.1 ;
                camera.rotation.z = 0 ;
                camera.rotation.x = 0 ;
            }
        }
           }
controlers[0] = 0; 

    }
      }
   }
    
function handleSticks(axes, indexid) 
    {
        updateStickk("controller-b10", axes[0], axes[1], indexid );
        updateStick("controller-b11", axes[2], axes[3], indexid );
    }

function gameLoop()
   {
      if (controllerIndex.length > 0)  
      { 
        for( var loop_id = 0; loop_id < controllerIndex.length; loop_id++)
        {
                const gamepad = navigator.getGamepads()[loop_id];
                gamepad.indexid = controllerIndex[loop_id];
            handleButtons(gamepad.buttons, gamepad.indexid);
                handleSticks(gamepad.axes , gamepad.indexid );
        }
       }
    requestAnimationFrame(gameLoop);
   }

gameLoop();