RotateTowards taking the long way around

Hi,
I have an object A and a rotation to a target object B (calculated with Quaternion.LookRotation), I would like to rotate the object A in a way to match the rotation of the target object B but rotating it through the longest way around.

I’ve read in another post that RotateTowards (the function I use the most with rotations) can only rotate through the shortest way.

As an alternative I could settle with Rotate but I’m having problems with stopping the rotation at the right place (objectA.transform.rotation == targetObjectB.transform.rotation doesn’t work).

Thank you for help

Bye

Not sure you can get what you want with that flavor, which works with Vector3.

You should be able to do it by perhaps combining the above with Quaternions however:

  • do the RotateTowards() to find the “closer” or “shorter” step towards goal

  • use Quaternion.FromToRotation() but tell it to go the other way

  • use that rotation on your original vector

  • lather rinse repeat

Comparing floating point numbers for equality is never a good idea.

Floating (float) point imprecision:

Never test floating point (float) quantities for equality / inequality. Here’s why:

https://starmanta.gitbooks.io/unitytipsredux/content/floating-point.html

https://discussions.unity.com/t/851400/4

https://discussions.unity.com/t/843503/4

Comparing for close enough is likely going to be what you want.

RotateTowards uses Slerp under the hood and Slerp does a check and invert to always take the short way around. This check can be inverted to always take the long way.

Here’s a custom Slerp implementation that has an option to use the long way:

/// <summary>
/// Custom Slerp implementation that allows to go the longest instead of the shortest way.
/// </summary>
public static Quaternion Slerp(Quaternion start, Quaternion end, float pos, bool shortWay = true)
{
    var dot = Quaternion.Dot(start, end);
    if ((shortWay && dot < 0) || (!shortWay && dot > 0)) {
        start = ScalarMultiply(start, -1f);
        dot *= -1f;
    }

    dot = Mathf.Clamp(dot, -1f, 1f);
    var theta0 = Mathf.Acos(dot);
    var theta = theta0 * pos;

    var s1 = Mathf.Sin(theta)  / Mathf.Sin(theta0);
    var s0 = Mathf.Cos(theta) - dot * s1;

    return Quaternion.Normalize(Add(ScalarMultiply(start, s0), ScalarMultiply(end, s1)));
}

/// <summary>
/// Multiply all components by a scalar value.
/// </summary>
public static Quaternion ScalarMultiply(Quaternion q, float scalar)
{
    q.x *= scalar;
    q.y *= scalar;
    q.z *= scalar;
    q.w *= scalar;
    return q;
}

/// <summary>
/// Add two quaternions.
/// </summary>
public static Quaternion Add(Quaternion one, Quaternion two)
{
    one.x += two.x;
    one.y += two.y;
    one.z += two.z;
    one.w += two.w;
    return one;
}

You can see how RotateTowards is implemented in Unity’s C# source repository. Reimplementing it using the custom Slerp method above and adding the shortWay argument shouldn’t be very difficult.

3 Likes

As unlikely as this scenario is, does the above behave properly (both shortWay and !shortWay) if somehow magically you end up with a dot product of zero?

I’m thinking it might arbitrarily “snag” on one side of things… unless these are really just two separate code pathways that are forked by shortWay… maybe I need to pour more coffee in the hole and think harder about it.

A dot product of zero means the rotations are opposite to each other. In which case there is neither a shortest or longest way, all ways are the same distance. It’s not really defined which rotation Slerp will us in this case, so inverting or not should not make a difference.

Er, wouldn’t a dot of zero be 90 degrees? Dot of -1 is opposite… dot of 1 is equal…

Yes, but that’s the angle between the two quaternions in 4D space, not between the 3D rotations the two quaternions represent. If you look at Quaternion.Angle, you can see that the sign of the dot product is discarded and the angle doubled. So a 90° angle between the quaternions in 4D space is actually a 180° angle in 3D space.

1 Like

Thank you for all the answer as always.

Interesting, I’m gonna check this.

As a workaround at the moment I’m using Rotate, checking every frame if the Quaternion.Angle between object A rotation and target object B rotation is less than an epsilon value of 1.5f, if it is than I snap the rotation with objectA.transoform.rotation = objectB.transform.rotation.
I know that it’s not the most elegant solution but this is what I came up with for now.

That’s nice, I will try it.

And if I want to force a direction even with dot product == 0? For example that same direction that it will take in the “longest way” case? Is it possible?

Thank you all again for the help

Bye

This doesn’t really help much because RotateTowards is essentially state-less. The only state that is preserved is the current rotation. So you can never rotate around the long way without any additional state information. That’s because you can “start” rotating towards the long way, but as soon as you crossed the 180° mark, the next call would rotate backwards since you want to rotate the long way. So this would jitter around 180° and never reaches the target.

RotateTowards and MoveTowards rely on the fact that it can reconstruct the curve / path it should move along from the current position and the target position. So this approach would never work. You would need an actual fix start and fix end rotation in addition to the current rotation in order to reliably rotate from the start to the end the long way.

Not sure what you mean. If the dot product is (near) zero, the two rotations are opposite and all direct ways from one rotation to the other will be 180° long. There’s no longer a “longest way” case because all ways are the same length.
If you want to define where to rotate through in that case, you’d have to pick an intermediate rotation and do two Slerp.

True! My bad.

You could use the long-way Slerp I posted above with 0.5 to find the mid-rotation of the long way and then first rotate to the mid-rotation and then to to the target rotation with RotateTowards.
But then it might be easier to use the long-way Slerp directly and use 360 - Quaternion.Angle(a, b) to calculate the angle distance and figure out how long the Slerp needs to be to have a constant speed as with RotateTowards.

2 Likes