three.jsrotationeuler-anglesdat.gui

Sequentially rotate threejs object around world axes (extrinsic Euler angles)


I am creating a visualization of extrinsic Euler angle rotations, and I want an animation where a cube rotates around the world axes depending on user input. I have a dat.GUI control with x, y, and z rotation angle controls. See this screenshot.

So far I have been able to implement intrinsic rotations, simply using cube_axes.rotation.x (and .y, .z) and setting the cube rotation. My GUI code looks like this:

gui.add(params, 'x_rot').name('X rotation').min(-180).max(180).step(5).onChange(() => {
        cube_axes.rotation.x = toRadians(params.x_rot)
})
gui.add(params, 'y_rot').name('Y rotation').min(-180).max(180).step(5).onChange(() => {
        cube_axes.rotation.y = toRadians(params.y_rot)
})
gui.add(params, 'z_rot').name('Z rotation').min(-180).max(180).step(5).onChange(() => {
        cube_axes.rotation.z = toRadians(params.z_rot)
})

When the user moves the x rotation control, the cube sets its rotation around its local x-axis by the specified degree. Then, when the user moves the y rotation control, the already-rotated cube rotates around its local y axis.

The issue that I'm encountering with extrinsic rotations, however, is that the cube erases previous rotations (essentially resetting itself) and simply rotates the control being changed. As a result, the cube never rotates on top of the previous rotation. This is my code:

gui.add(params, 'x_rot').name('X rotation').min(-180).max(180).step(5).onChange(() => {
        //cube_axes.setRotationFromAxisAngle(x_vector, toRadians(params.x_rot))
        let quaternion = new THREE.Quaternion().setFromAxisAngle(x_vector, toRadians(params.x_rot));
        cube_axes.rotation.setFromQuaternion(quaternion);
})
gui.add(params, 'y_rot').name('Y rotation').min(-180).max(180).step(5).onChange(() => {
        //cube_axes.setRotationFromAxisAngle(y_vector, toRadians(params.y_rot))
        let quaternion = new THREE.Quaternion().setFromAxisAngle(y_vector, toRadians(params.y_rot));
        cube_axes.rotation.setFromQuaternion(quaternion);
})
gui.add(params, 'z_rot').name('Z rotation').min(-180).max(180).step(5).onChange(() => {
        //cube_axes.setRotationFromAxisAngle(z_vector, toRadians(params.z_rot))
        let quaternion = new THREE.Quaternion().setFromAxisAngle(z_vector, toRadians(params.z_rot));
        cube_axes.rotation.setFromQuaternion(quaternion);
})

I've tried using .setFromQuaternion (from this question) and .setRotationFromAxisAngle (the commented-out line), but both methods had the same issue. I'm not sure what's going on here. Is it because I'm using .rotation instead of specific rotation axes (.rotation.x, .rotation.y, .rotation.z)?

Thanks.


Solution

  • I figured it out! Instead of .setFromQuaternion, use .applyQuaternion. From my understanding, this applied an rotation on top of what was already there, rather than resetting it completely. Here is my code:

    gui.add(params, 'x_rot').name('X rotation').min(-180).max(180).step(5).onChange(() => {
        let quaternion = new THREE.Quaternion().setFromAxisAngle(x_vector, toRadians(params.x_rot));
        cube_axes.applyQuaternion(quaternion);
    })
    gui.add(params, 'y_rot').name('Y rotation').min(-180).max(180).step(5).onChange(() => {
        let quaternion = new THREE.Quaternion().setFromAxisAngle(y_vector, toRadians(params.y_rot));
        cube_axes.applyQuaternion(quaternion);
    })
    gui.add(params, 'z_rot').name('Z rotation').min(-180).max(180).step(5).onChange(() => {
        let quaternion = new THREE.Quaternion().setFromAxisAngle(z_vector, toRadians(params.z_rot));
        cube_axes.applyQuaternion(quaternion);
    })
    

    Note that this implementation doesn't use the rotation property of the THREE Object.

    Edit: for those who are attempting to do the same thing, you also need an extra variable that keeps track of the previous parameters, so that you can apply the correct angle. Otherwise, you end up with an incorrect rotation (one that continues to add another angle whenever the control changes). See the code below:

    let prev_params = {
        x_rot: 0,
        y_rot: 0,
        z_rot: 0,
    }
    

    Then this code in the GUI onChange function (I only included the code for x):

    let angle = toRadians(params.x_rot)
    if (prev_params.x_rot) {
        angle = angle - toRadians(prev_params.x_rot)
    }
    let quaternion = new THREE.Quaternion().setFromAxisAngle(x_vector, angle)
    cube_axes.applyQuaternion(quaternion)
    
    prev_params.x_rot = params.x_rot