Issue animating camera direction via tween.js

I have a task that I need to animate camera position and direction from current values to target values.

These target values are stored in a json format (I’ve verified they are correctly being fetched from the json file)

This code correctly animates the position from the current to the target position via tween.js , but not the target direction. The final direction never matches the target direction, and the final direction always depends on the starting conditions. (this should not be - desired behavior is for the direction to always match the target direction found in the json file, no matter the starting direction)

I have this working without animation as well

The animation code is below, with the working (non animated) code below it.

fetch('source.json')
  .then(response => response.json())
  .then(data => {
    // Populate the menu buttons
    data.menuItems.forEach(item => {
      const menuButton = document.createElement('button');
      menuButton.textContent = item.title;
      menuButton.addEventListener('click', () => {
        const { x: initialX, y: initialY, z: initialZ } = camera.position; // this is the intial camera position
        const { x: finalX, y: finalY, z: finalZ } = item.cameraPosition; // this is the target camera position from json file
        console.log("camera position(" + camera.position.x +","+camera.position.y+","+camera.position.z+")")
        console.log("item.camera position (" +item.cameraPosition.x +","+item.cameraPosition.y+","+item.cameraPosition.z+")")
        
         // Extract initial and final camera directions
         const initialDirection = camera.getWorldDirection(new THREE.Vector3()); // this is the intial camera direction
         const finalDirection = item.cameraDirection; // this is the final camera direction
         console.log("camera direction(" + initialDirection.x +","+initialDirection.y+","+initialDirection.z+")") 
         console.log("finaldirection(" + finalDirection.x +","+finalDirection.y+","+finalDirection.z+")") 
         console.log("item.camera direction(" +item.cameraDirection.x +","+item.cameraDirection.y+","+item.cameraDirection.z+")")

        controls.enabled = false;

        const startTime = performance.now(); // Record start time

        const tween = new TWEEN.Tween({ x: initialX, y: initialY, z: initialZ })
          .to({ x: finalX, y: finalY, z: finalZ }, 1000)
          .easing(TWEEN.Easing.Quadratic.InOut)
          .onUpdate(() => {
            const currentTime = performance.now(); // Get current time
            const elapsed = currentTime - startTime; // Calculate elapsed time
            const progress = Math.min(elapsed / 500, 1); // Calculate progress (500 is the duration)
            
            const interpolatedDirection = new THREE.Vector3().lerpVectors(initialDirection, finalDirection, progress);
            // Log interpolated direction values to the console
            console.log("Interpolated Direction:", interpolatedDirection.x, interpolatedDirection.y, interpolatedDirection.z); 
            camera.position.set(tween._object.x, tween._object.y, tween._object.z);   
            const targetPosition = interpolatedDirection.add(camera.position);
            camera.lookAt(targetPosition);         
            //camera.lookAt(interpolatedDirection.add(camera.position)); //

          })
          .onComplete(() => {
            //camera.lookAt(interpolatedDirection);
            controls.enabled = true;
          })
          .start();
      });
      menuButtonsContainer.appendChild(menuButton);
    });
  })
  .catch(error => console.error('Error fetching menu data:', error));

// Render loop
function animate() {
  requestAnimationFrame(animate);
  TWEEN.update();
  controls.update();
  renderer.render(scene, camera);
}
animate();

working, non animated code:

fetch('source.json')
  .then(response => response.json())
  .then(data => {
    // Populate the menu buttons
    data.menuItems.forEach(item => {
      const menuButton = document.createElement('button');
      menuButton.textContent = item.title;
      menuButton.addEventListener('click', () => {
        const { x, y, z } = item.cameraPosition;
        const { x: dirX, y: dirY, z: dirZ } = item.cameraDirection;

        camera.position.set(x, y, z);

        const direction = new THREE.Vector3(dirX, dirY, dirZ);
        camera.lookAt(direction.add(camera.position));
      });
      menuButtonsContainer.appendChild(menuButton);
    });
  })
  .catch(error => console.error('Error fetching menu data:', error));

Most likely something else in your code is interfering with the camera, because when I try your code, it works. As you are the only one that can debug the full code, it is up to you to find the bug.

Here is your code used in a page of mine. The target position and directions are set manually in lines 99-102, and your code is in lines 104-143:

https://codepen.io/boytchev/pen/ExJGpGG?editors=0010

Also, I made a shorter implementation of tweened camera animation (see lines 98-113):

https://codepen.io/boytchev/pen/dyLwjJE?editors=0010

3 Likes

This solution worked. Thanks!

When I first click the screen after animating, though, the camera jumps to a different position. Is there a way to fix this?

I believe this problem is described here as well

controls.target.set(0,0,-controls.target.distanceTo(camera.position)).applyQuaternion(camera.quaternion).add(camera.position)
controls.update();
1 Like

Perfect. This works thank you!

1 Like