Trouble with using look-around.js and tweening camera rotation together

I really like the way look-around.js in this project works to rotate the camera:

I’m currently working on implementing it as a first person camera controller in a click and drag/hotspot to move scene.

I’ve modified the script to only set the localEulerAngles of the camera when the scene is not in the process of tweening the camera and it’s parent around:

LbLookAround.prototype.update = function (dt) {
    // Update the camera's orientation
    this.ex = pc.math.lerp(this.ex, this.targetEx, dt / 0.2);
    this.ey = pc.math.lerp(this.ey, this.targetEy, dt / 0.2);
    if (! && !this.relocating) {
        this.entity.setLocalEulerAngles(this.ex, this.ey, 0);
        // console.log(this.entity.getLocalEulerAngles());

At the end of my tween, I fire an event that resets this.ex and this.ey along with their respective targets, which means the user can pickup where the tween left off and continue looking around:

LbLookAround.prototype.onMoveComplete = function() {
    var quat = this.entity.getLocalRotation();
    this.ex = this.getPitch(quat) * pc.math.RAD_TO_DEG;
    this.ey = this.getYaw(quat) * pc.math.RAD_TO_DEG;
    this.targetEx = this.ex;
    this.targetEy = this.ey;
    this.relocating = false;  

This mostly works. As long as x and z are both either 0 or 180, the tween ends and and the user can look around as expected. The problem I run into is if I deviate from this, the camera snaps away from the intended position after the tween ends.

I know that look-around.js is setting z rotation to 0 in the update function, and console.logging ex and ey and using those values as tween targets to keep z at zero does not seem to be solving the problem either. It still snaps after the tween ends to a completely parallel to the ground position.

Any insights would be greatly appreciated.

Thank you!

Would you be able to provide an example of the issue? I’m not sure what you mean by ‘if I deviate from this’

I think I’ve slimmed down this scene enough to post:

You will see that if you click the hotspot on the floor to the right it is set to tween the camera’s localEulerAngles to 180, -56.7, 180 before returning control to my modified look-around.js “lbLookAround.js”. When the rotation tween is completed, the look around script takes over as if nothing happened.

If you instead try the hotspot on the floor to the left, when the rotation tween is completed, the camera snaps back up to a different position after the tween, but will otherwise allow the user to continue looking around. This happens after resetting ex and ey, along with their targets, even though the z rotation channel is set to zero as is defined in the update function.

Any thoughts would be greatly appreciated.

This is probably due to the Quaternion to Euler conversion. Hmm, considering what you are trying to achieve, this method will probably not going to work. I am talking about accumulating the eulers on update and setting a camera rotation to it. The free look on its own or the tween on its own would work fine, but converting the states from one to another will cause issues.

What you can do is you can add an empty entity as a child to the camera. It would be used as a pivot point that the camera will always look at. To rotate the camera with a mouse you would use the axii deltas to translate the pivot around the camera. You can google how to move a point on a sphere surface.

This will work, because when you tween the camera rotation, the pivot point as a child will also move. Once the tween ends, the pivot point will end up in the new position and the camera will be facing correctly, which you can continue rotating from there.


It looks like these two functions are outputting incorrect rotations:

// Taken from
// Not completely accurate, usually a couple of degrees out
LbLookAround.prototype.getYaw = function (quaternion) {
    var q = quaternion;
    var x = q.x * q.x;
    var y = q.y * q.y;
    return Math.atan2(2 * q.y * q.w - 2 * q.z * q.x, 1 - 2 * y - 2 * x);    

LbLookAround.prototype.getPitch = function(quaternion) {
    var q = quaternion;
    return -Math.asin(2 * q.z * q.y + 2 * q.x * q.w);
1 Like

I used the functions from the Model Viewer template and it works now :slight_smile: - I think I ran into this issue before and changed it to be based on the forward vector instead:



Thank you very much for the explanation and suggestion! I was actually about to try and tackle your suggestion this morning…



Thank you very much for this! I will take the time to study the changes and make sure I understand the differences in calculation.

Thank you again!

1 Like