How to smoothly rotate between Quaternions

I am trying to figure out the best way to gradually rotate my characters and spells in a way that is consistent as the framerate changes.

In the past, I have just used the Vector3f.interpolate() method to interpolate from a start view direction to the final view direction, but using that method interpolates based on a percentage, and this means that even when altering the interpolation percentage based on tpf, I always end up with inconsistent rotation speeds at different framerates depending on the total difference between the current and target rotation.

I have also tried this code for a spell that locks onto a target and adjusts its trajectory using a rotationSpeed… and it almost works, but there’s a weird bug where the projectile sometimes rotates the long way around, rather than the short way. (i.e. the spell could turn right to get to me faster, but instead turns left and does a figure 8 before coming back at me)

 public void alterProjectileDir(Vector3f locationToSeekTo, float tpf){
        Vector3f projectileLoc  = spellNode.getWorldTranslation(); //this is the current location of the spell in world space.
    
        Vector3f directionToTarget = (locationToSeekTo.subtract(projectileLoc));
        directionToTarget.normalizeLocal();
        

      //rotationalNode is used as a dummy node for its lookAt() method to calculate both the current and target quaternions 
       
        
      //calculate and store the currentRotation        
        rotationalNode.setLocalTranslation(projectileLoc);
        rotationalNode.lookAt(projectileLoc.add(movementDirection), Vector3f.UNIT_Y);
        Quaternion currentRotation = rotationalNode.getWorldRotation().clone();
        
      //calculate and store the targetRotation
        rotationalNode.lookAt(locationToSeekTo, Vector3f.UNIT_Y);
        Quaternion targetRotation = rotationalNode.getWorldRotation().clone();
        
        targetRotation.normalizeLocal();
        currentRotation.normalizeLocal();//not sure if i need to normalize these here, but the result is the same even when i don't
        
      //subtract currentRotation from targetRotation to get the total rotational difference
        Quaternion rotateValueForFrame = targetRotation.subtract(currentRotation);          
        rotateValueForFrame.normalizeLocal();
        
      //scale this rotational difference by tpf and speed so it rotates smoothly over time
        rotateValueForFrame.multLocal(tpf * rotationSpeed);

      //apply new rotation to the rotationalNode        
        rotationalNode.setLocalRotation(currentRotation.add(rotateValueForFrame));

      //set the current movement direction to move in the direction the rotationalNode is facing
        movementDirection = rotationalNode.getWorldRotation().getRotationColumn(2);
        movementDirection.normalizeLocal();
        movementDirection.multLocal(speed);
        
      //move spell
        spellNode.move(movementDirection);
}

Is there a better way to do this? Or is there possibly something wrong with my code causing the bug I described?

For some reason I think that the bug causing it to rotate around the long way is an unavoidable symptom of the way I’m subtracting two quaternions, in which case I would greatly appreciate any advice or explanations on how to achieve smooth rotation with tpf in a better way.

Thanks :slightly_smiling_face:

edit: accidentally said I was trying quaternion.interpolate (which doesn’t exist), but I meant to say I was trying to interpolate between 2 vectors that represent view directions, but couldn’t figure out how to use tpf correctly to adjust the itnerpolation value every frame

1 Like

Yeah, because for rotations, this is essentially nonsense:

It’s allowed because quaternions are “like” vec4 in a sense… but from a rotational sense, I’m super surprised that it works at all.

I don’t know enough about your inputs to know what you are trying to achieve specifically, but one approach is to go back to where you started: interpolation but change the way you think of it.

q1 at time1, q2 at time2…

t = (currentTime - time) / (time2 - time1);
interpolate with that t.

…then frame rate won’t affect things anymore.

Edit: and if it’s the case that you’re trying to have one thing follow something else that is moving then that’s a whole different thing that is better off using some kind of steering approach.

2 Likes

Yes right now I’m trying to get a magical projectile to follow a target so its harder to dodge, but I also am planning to do something similar for player and NPC movement so that a fast moving npc cannot rotate 180 degrees in a split second while chasing the player, similar to the way a fast moving animal or car has to make wider turns in real life.

Do you have any good examples or sources that you could recommend for learning more about steering?

I have been meaning to look more into steering eventually in order to solve another issue with all of my NPCs walking into each other when they are clumped up and are chasing the player. Unless I’m mistaken I think this is also something that should be solved by having a good steering system.

I also think I can get the seeking projectile spell to work using interpolate like you recommended, since the seeking for my spell is mostly simple. But I’m still interested in figuring out more about steering for npcs.

Thanks for you help :slightly_smiling_face:

I don’t know of a good steering resource though there will be plenty online. I learned about it way back in the days of C coding. :slight_smile:

In simplest terms:
you have some function/strategy object that provides a steering value, say -1 being maximum left and 1 being maximum right, 0 being no steering at all.
The mob is then driven by this steering value and their turning characteristics… for example, maybe they can only turn a max of X degrees or they have a turning acceleration. What to do with that steering value varies based on the mob.
Similarly there is usually an acceleration/braking function… same 1 = max acceleration, -1 being max braking. What to do with those values depends on the mob physics just like for turning.

Once you have a mob that is driven by a steering value and a acceleration/braking value then you can supply different functions (or combinations of functions) based on need.

A simple following steering behavior might use a “right” vector to dot with the relative location of the target.
Pseudo code for simple following a target:

relative = targetPos.subtract(mobPos);
float accel = forward.dot(relative);
relative.normalizeLocal();
float steering = right.dot(relative);
if( accel < 0 ) {
   // Object is behind us so use maximum steering
   steering = sign(steering);
   accel = 0;
} else {
   accel = min(1, accel);
}

So if the target is to the right, steering will be 0…1 depending on how much right (with 90 degrees right being 1), 0…-1 for left.

Accel will be 0…1 with 0 being “behind” or “right under the mob” and 1 being 1 meter away or more. Adjust the math accordingly if the mob should start slowing down at farther than 1 meter.

Hopefully that example is enough to get the gears turning.

2 Likes

Back then these tutorials helped me understand steering behaviors, I am putting a link to them here if you want to take a look:

Gdx-AI has an implementation

Another implementation is the JME-Steer library

2 Likes