Struggling with rotating (and animating) using .rotate() and quaternions

Hey everyone,

I am working on a project with a cylindrical mesh that rotates using the mouse scroll and also when the mouse is clicked and dragged. To achieve this, I call a method that takes the amount scrolled/dragged, does some calculations to get a correctly scaled number and uses that to rotate the mesh.

do some calculations on amount.y and then pass to this method
rootMesh.rotate(Vector3.Right(), -amount.y, Space.WORLD)

We would like to add functionality to where you can rotate to specific points on the cylinder and also check what the current rotation is.

The way I picture it in my head, is the cylinder starts at like 0 degrees and can rotate to 270 degrees. You can scroll up and down, or use the mouse to rotate between these two values. We would then have specific points to rotate to: i.e. Section A is starts at 20 degrees, Section B at 90 etc.

However, I am struggling to use quaternions to determine where we are at in the rotation and also how to rotate to a specific point on the cylinder.

What can I do with quaternions to say rotate (and animate the rotation) to a specific point, where the mesh only rotates in 1 axis.

Any help would be appreciated, thank you

Hello @callumdotexe , how are you doing? Would you mind setting up a Playground for that? I have the feeling that it will be easier to have a conversation about this using a Playground as a reference.

Hello, I will try and set up a playground, however for some more information:

The cylinder is called rootMesh and sits at the centre of a model our 3D team built. When the user scrolls or clicks and drags, the amount moved is calculated and passed to the below function:

 rotateGlobe(amount: Vector2) {
      if (rootMesh && rootMesh.rotationQuaternion) {
        // const canMoveUp = rootMesh.rotationQuaternion.z > 0.325 && amount.y > 0
        // const canMoveDown = rootMesh.rotationQuaternion.z < 0.7 && amount.y < 0
        let canMoveUp
        if (rootMesh.rotationQuaternion.y < 0) {
          // was 0.6898
          canMoveUp = rootMesh.rotationQuaternion.z > 0.6948 && amount.y > 0
        } else {
          canMoveUp = rootMesh.rotationQuaternion.z > 0.3 && amount.y > 0
        }
        let canMoveDown
        if (rootMesh.rotationQuaternion.y < 0) {
          canMoveDown = rootMesh.rotationQuaternion.z > 0.5 && amount.y < 0
        } else {
          // was 0.47
          const zAmount = this.isMobile ? 0.51 : 0.42
          canMoveDown = rootMesh.rotationQuaternion.z > zAmount && amount.y < 0 && rootMesh.rotationQuaternion.z + amount.y > zAmount // fixes issues with flicking the mouse
        }
        if (canMoveUp || canMoveDown) {
          rootMesh.rotate(Vector3.Right(), -amount.y, Space.WORLD)
          // this.resetTimer()
        }
      }
    },

I understand this is not the cleanest solution, which is one of the reasons I wanted to see if there was a better way to rotate the mesh. With the quaternion, I have to check if the Y is positive since it flips between positive/negatives. In a perfect world, I would be able to rotate on the X axis from like 0 - 270 degrees or whatever and also have a function to rotate to a certain amount.

Will update with playground if I have time.

I would solve it like this, it is open to be customized to your needs like only passing rotation angles and do conversion to quaternion inside spinTo-function:

1 Like

Thank you @Takemura, this is what I was hoping for.

Yes, no problem :slight_smile: . With toEulerAngles() you can convert from Quaternion to euler angles and with Quaternion.FromEulerAngles() you can convert oppositely.

One thing I have noticed with quaternions is when I am at a certain rotation, the angle I pass to the spin to function will have instead spin in the opposite direction. Is there a way to stop this, other than checking whether the other axis has gone negative.

Also is it correct to pass the angle as degrees converted to radians?

i.e. toAngle = Tools.ToRadians(-20)

Thanks

Maybe an rotation offset of +PI in temporary angle-variable and using toAngle = BABYLON.Scalar.Repeat(tmpAngle, 2*Math.PI) as floating point modulo operator, helps out in this case?

From my knowledge this should be correct even for negative values.

Edit: Another idea would be:
When slerping between two rotations q0 qnd q1, Dot(q0, q1) must be positive. If not, replace q1 with -q1.

Here the docs, how to change quaternionInterpolateFunction:

1 Like

Thanks for the responses, but could you provide an example? I am having trouble understanding, sorry.

Add this to your code to change animation function for quaternion (interpolation):

BABYLON.Animation.prototype.quaternionInterpolateFunction = function (startValue, endValue, gradient) {
  const resultValue = endValue.clone()
  if(BABYLON.Quaternion.Dot(startValue, endValue) < 0) {
    resultValue.scaleInPlace(-1)
  }
  return BABYLON.Quaternion.Slerp(startValue, resultValue, gradient);
};

A rotation can be represented by 2 quaternions, one is the “long” way. To always take the “short way”-representation you need above change. If you want to know more you might have to look into math.

So adding this and using the same function you defined earlier (spinTo) should just work?

Yes, but I am not sure if I got this right, should it be:

resultValue.scaleInPlace(-1)

or

resultValue = resultValue.invert()

Maybe you can try it out?

.invert() does not exist on Quaternions (which is the type of result value)

the scale in place also does not seem to have the effect I was hoping for

Ah, I read your post again. Do you want to always rotate positively or negatively (in one direction), regardless of shortest/longest way? Then you would have to take into account if it would rotate clockwise or counterclockwise. But from what I know this is not simple, because you need a point of view (plane like XY or normal/axis) for all x,y,z. If this is not the case, you might be able to show me a playground, where the issue occurs?

.invert() exists on Quaternion:

1 Like

So currently when I spinTo using toAngle = Tools.ToRadians(-20), the mesh rotates in the way I’d expect. However, when the mesh is at a certain rotation, using the same rotation function with the same toAngle moves it down rather than moving it back up to where I want.

I think this is because the rotationQuaternion at that specific angle has flipped and the mesh rotation is not taking that into consideration.

I will try to create a playground to explain what I mean.

I could just get the specific quaternions for each section and then animate to them that way