'How do I make GSAP find the "most optimal" rotation method?

I want my mesh to rotate at 90 degree angles in all directions, but make GSAP find the most "optimal rotation".

So if I press 1 it'll be at

  • this.meshName.rotation.y = 0

and 2

  • this.meshName.rotation.y = Math.PI/2

and 3

  • this.meshName.rotation.y = Math.PI

and 4

  • this.meshName.rotation.y = (3*Math.PI)/2

GSAP does not find the "most optimal rotation",

For example if I press 4 and then 1 after, it rotates like this (in red) enter image description here

and I want it to rotate like this (in green) enter image description here\

The only solution I can think for is making this.meshName.rotation.y = 2*Math.PI if the last key pressed was 4 (and then I press 1), but then I have to set it back to 0 after the animation otherwise it'll affect the other keys. What I have now only sometimes works but it's really messy. I feel like there's a better way to do this.:

this.testLastKey = false;
        document.addEventListener('keydown', function(event) {
            if(event.key === "1") {
                if(this.testLastKey){
                    GSAP.to(this.mesh.rotation, {
                        y: Math.PI*2,
                        duration: 0.2,
                    });
                    this.mesh.rotation = 0                
                } else {
                   GSAP.to(this.mesh.rotation, {
                        y: 0,
                        duration: 0.2,
                    });
                }
                this.testLastKey = false
            }
            else if(event.key === "2") {
                GSAP.to(this.mesh.rotation, {
                    y: Math.PI / 2,
                    duration: 0.2,
                });
                this.testLastKey = false
            }
            else if(event.key === "3") {
                GSAP.to(this.mesh.rotation, {
                    y: Math.PI,
                    duration: 0.2,
                });
                this.testLastKey = false

            }
            else if(event.key === "4") {
                GSAP.to(this.mesh.rotation, {
                    y: (3*Math.PI)/2,
                    duration: 0.2,
                });
                this.testLastKey = true

            }
        }


Solution 1:[1]

So the main issue here is that regular (Euler) rotation angles don't interpolate well when they have to cross zero.

Quaternions to the rescue!

I know, I know, quaternions are hard to understand. But no worries, just call on the quaternion helper methods to do the heavy lifting :-). Once you convert all angles to quaternions they interpolate really really well.

Second issue is that GSAP doesn't know quaternion values represent an angle, so the interpolation is really weird. It does the job, but the motion looks really weird. Best to just use the 3D library's quaternion interpolation methods (together with GSAP). Spherical linear interpolation (slerp) is used in this example


(ThreeJS implementation)

//OBJECTIVE
//User presses keys 1,2,3 or 4 which should rotate model's mesh to face 0,90,
//180 or 270 degrees respectively (rotating on the y-axis)

document.addEventListener("keypress", (event) => {
  //Taking advantage of integer keypress to simplify all rotation cases
  // into 1 case. For other cases like alphabet key presses you can create
  // a map of some sort

  const inKey = parseInt(event.key) - 1; //convert input key to 0 indexed 'iterator'
  const step = { factor: 0 }; //GSAP needs value set up as a property/object

  //prevent smart people from breaking your app with alphabet iterators :-)
  if (Number.isNaN(inKey)) return;

  //Create quaternion objects to store mesh's initial & final/target rotation.
  //Here, setFromEuler(x,y,z) converts a regular <x,y,z> rotation vector to
  // a quaternion <x,y,z,w>
  const initRot = new THREE.Quaternion().copy(mesh.quaternion);
  const targetRot = new THREE.Quaternion().setFromEuler(

    //Destination y-axis angle uses integer keypress input like an 'iterator'
    // current value, to make an implicit "map" for this exact use case/keypress.
    // Effectively 1=>0°,2=>90°,3=>180°,4=>270°,other input numbers modulo 4
    new THREE.Euler(0, (inKey * Math.PI) / 2, 0)
  );

  //GSAP tweening a number 'step.factor' from 0 to 1
  GSAP.to(step, {
    factor: 1, //use this factor to interpolate "manually"
    duration: 0.2,

    //Fake "render loop" :-) Interpolate using threeJS slerpQuaternion
    // since GSAP doesn't know its an angle and interpolates awkwardly :-(.
    // This way we can run a custom interpolate on tick
    onUpdate: () =>
      mesh.quaternion.slerpQuaternions(initRot, targetRot, step.factor),
  }); //end of GSAP.to
}); //end of addEventListener

TL;DR. Here's how to rotate 3D object "mesh" on keypress. (same but without comments)

document.addEventListener("keypress", (event) => {
  const inKey = parseInt(event.key) - 1; 
  const step = { factor: 0 }; 
  if (Number.isNaN(inKey)) return;

  const initRot = new THREE.Quaternion().copy(mesh.quaternion);
  const targetRot = new THREE.Quaternion().setFromEuler(
    new THREE.Euler(0, (inKey * Math.PI) / 2, 0)
  );

  GSAP.to(step, {
    factor: 1, 
    duration: 0.2,
    onUpdate: () =>
      mesh.quaternion.slerpQuaternions(initRot, targetRot, step.factor),
  }); 
}); 

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tim Affram