4

Scenario

Given a normalised quaternion, A_q, and a normalised direction vector, B_v, we calculate a new rotation, C_q, that will rotate A_q to "face" in the direction of B_v. To clarify, let D_q = C_q * A_q, such that (1,0,0) rotated by D_q will be equal to B_v.

N.B. For notational convenience, from now on we will refer to (1,0,0) rotated by any quaternion as that quaternion's symbol with the suffix _v, i.e. (1,0,0) rotated by A_q becomes A_v.

Demonstration

I have implemented such a scenario here (interactive), with source here, and have attached a gif below too.

enter image description here

My Problem

My problem is that when A_v and B_v are close to opposite (or seemingly approaching anti-parallel), D_q exhibits a sudden twist/roll, which is undesirable. The image above demonstrates this.

The code in question

It is in UE4's C++ API, but it should be language/framework agnostic enough to parse:

FTransform UMWEUtil::FindNewRotation(FTransform A_t, FVector B_v)
{
    FQuat A_q = A_t.GetRotation();
    FVector A_v = A_q.GetForwardVector();

    A_v.Normalize();
    B_v.Normalize();

    FVector AxisOfRotation = FVector::CrossProduct(A_v, B_v).GetSafeNormal();
    float Rads = FMath::Acos(FVector::DotProduct(A_v, B_v));
    FQuat C_q(AxisOfRotation, Rads);

    FQuat D_q = C_q * A_q;

    return FTransform(D_q);
}

My questions

  1. Why does this sudden roll/twist artifact occur?
  2. I am guessing it is because of cross product A_v X B_v changing hemispheres. But then, why does it only occur once, not twice (the cross product, A_v X B_v is seen transitioning hemispheres twice in a full rotation)?
  3. Finally, the solution I have come up with is to decompose D_q into swing/twist quaternions and only use the swing quaternion (thus, removing the sudden twist). However, this feels like a band-aid due to my lack of understanding of the problem, is there a different solution that will result in a smooth transition?

My solution (maybe unnecessary?)

FTransform UMWEUtil::FindNewRotation2(FTransform A_t, FVector B_v)
{
    FQuat A_q = A_t.GetRotation();
    FVector A_v = A_q.GetForwardVector();

    A_v.Normalize();
    B_v.Normalize();

    FVector AxisOfRotation = FVector::CrossProduct(A_v, B_v).GetSafeNormal();
    float Rads = FMath::Acos(FVector::DotProduct(A_v, B_v));
    FQuat C_q(AxisOfRotation, Rads);

    FQuat D_q = C_q * A_q;

    FQuat OutSwing;
    FQuat OutTwist;

    D_q.ToSwingTwist(FVector::ForwardVector, OutSwing, OutTwist);

    return FTransform(OutSwing);
}
Community
  • 1
  • 1
Lukehb
  • 454
  • 5
  • 15
  • Have you ever consider Euler (gimbal lock) problem. I suppose you are using a hardware for doing this scenario if you are using any gyroscope you need to contribute a compass with overcome this error – Cagri Candan Nov 27 '18 at 08:07
  • I have considered the gimbal lock problem, but I am using quaternions so I thought it would not happen? My solution is fully software (no compass or gyro involved), sorry if the vive controller in the gif misled - it is just for demonstration purposes. – Lukehb Nov 27 '18 at 11:23
  • 1
    The original problem is underspecified. There are an infinite number of rotations which takes a vector into another. I don't really understand the problem. All possible rotations will "contain" a rotation around the cross product (I mean, all possible rotations are: A*R*B, where R is the rotation around the cross product, and A and B are rotations around the source and destination vectors). And the cross product will behave as you recorded it on that picture. Note, that your rotation calculation code is inefficient: it can be done without any trigonometric functions. – geza Nov 27 '18 at 13:25
  • @geza You are right, the original problem is underspecified (this math is not my strong suit) - perhaps with some back-and-forth I can communicate my problem better. To begin, I realise there is an infinite number of rotations between two vectors. In my use-case, I suppose I am interested in finding a rotation that does not introduce twist to the final rotation (i.e. I do not want the sudden twist as pictured above). In regard to your advice about calculation inefficiency can you elaborate or give me some search term for this alternate formulation? – Lukehb Nov 27 '18 at 21:34
  • I think that you should show us the effect of twist. In the picture, if I'm not mistaken, the white arrow is the cross product. You cannot do anything about it. That vector will always be the same (the only thing that you can do is to flip it, to point at the reversed direction). If by twist you mean something else (not the movement of the cross product), then please show the full coordinate system (not just the X axis of it). About the alternate formulation, check this: https://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another – geza Nov 27 '18 at 22:49
  • @geza Thanks for the link. Yes, the white arrow is the cross product of the two vectors. By twist, I mean if you look at the gif, there is a black object (with green arrow), it experiences a rapid change in roll when the vectors approach anti-parallel. To clarify, this object's rotation is D_q (despite the label C, woops). This twist can be numerically observed in the "Rotation C =" string if you look at the R (roll) value over the course of the gif. If you would like additional values output in the gif please indicate which ones specifically, i.e the quaternion of A or C perhaps? – Lukehb Nov 27 '18 at 23:19
  • If this rapid change in roll value is caused by the change of the cross product over time, why then, do we not observe this rapid change in roll twice in the animation? After all, the cross product transitions hemispheres twice in the gif. – Lukehb Nov 27 '18 at 23:21
  • Ah, okay, now I see what you mean. That black object is hardly noticeable on my monitor (I use a very dark brightness). The question is, is it really needed to solve this problem this way? Because if you just need that black object to point into a specific direction, with stable *Roll* (https://en.wikipedia.org/wiki/Aircraft_principal_axes), then there is a more simple solution. – geza Nov 27 '18 at 23:30
  • @geza I am very open to other solutions. Please elaborate, are you saying I should solve this problem using Euler angles or Tait-Bryan angles? – Lukehb Nov 27 '18 at 23:34
  • No. I mean a simple *lookAt* matrix. Which is usually used for camera setup. This solution needs a direction vector, and an *up*-vector (this is responsible for the stable roll). This solution has a problem, which need to be solved: when the direction vector coincides the up vector. – geza Nov 27 '18 at 23:43
  • @geza Yes, I believe that is a fine solution. I have read this [answer](https://gamedev.stackexchange.com/questions/136174/im-rotating-an-object-on-two-axes-so-why-does-it-keep-twisting-around-the-thir), which I believe is a similar solution. My main reason for posting this question though is to understand this specific behaviour with quaternions and rotations that I have captured here. In this case, while I am open to alternate solutions, they are less important to me than understanding why this sudden roll is happening. I would like to deepen my understanding of this type of problem. – Lukehb Nov 27 '18 at 23:53
  • About that answer: It's a different approach. Maybe, in the end, it generates the same matrix, though. I don't think that sudden roll is unique to quaternions. I think you'll get the same, if you use matrices (Rodrigues' formula). The sudden roll happens, because during a 360 rotation, the object takes a full rotation around its axis too. You can see, it's constantly rotating around the green arrow. It's just happening, that it gets a faster rotation at a certain point, so it is sudden. It is more of the behavior of the "rotation from vector to vector" formula (not a matter of quat or matrix). – geza Nov 28 '18 at 00:07
  • @geza That is very useful to know, thank-you. It seems that any solution, then, will somehow have to stabilise the roll when rotating from vector to vector. If you know, could you explain why the speed of rotation around the green arrow gets faster at this certain point? I am guessing, it is tied to the change in the cross product? – Lukehb Nov 28 '18 at 00:24
  • Good question :) I'd need to reproduce your results, to be able to analyze what's going on (but it would need too much effort from me, sorry). Note, that it is not 100% sure, what I said in my previous comment is accurate. There's a little chance that there's something about quaternions (or your code, or UE4 code) which causes this exact behavior. – geza Nov 28 '18 at 00:31
  • @geza If you do end up reproducing it and analyzing, I would be very interested. However, in any case, thank-you for your time and thoughtful comments thus far. – Lukehb Nov 28 '18 at 00:35
  • I'm late here but I've also encountered this problem and I think I understood the problem. I think that there is not enough information to find the rotation that you want from only two vectors. Here is an example: place your phone on the table facing up. then flip your phone 180 degrees on its side (X axis). now your phone is facing down, the right vector of the phone is flipped by 180 (Y axis flipped). now put your phone on the table again as before facing up but this time rotate the phone on the Z axis by 180 degrees. your phone is still facing up, but Y axis is flipped. – M.kazem Akhgary Aug 25 '22 at 07:44
  • As you can see, Right Vector `(0, 1, 0)` was changed to `(0, -1, 0)` no matter when you flipped your phone on it's side (X axis) or rotated it by 180 degrees on Z axis,. in real world your phone ended up either facing down or facing up but Y axis turned to `(0, -1, 0)` in both cases. So if we wanted to just find a rotation from `(0, 1, 0)` to a `(0, -1, 0)` you can see that there are many ways to get to that direction. I think the only way to get the desired rotation in your situation is to use the current rotation and just rotate from there. (`RInterp` comes to mind) but I might be wrong. – M.kazem Akhgary Aug 25 '22 at 07:56
  • also vectors don't have roll information. so you in the phone example, once you flip the phone on X axis so that Y is flipped, you can keep rotating your phone on Y axis to get a whole range of possible rotations that still have `RightVector = (0, -1, 0)`. so maybe with additional vector you could define a desired roll for your rotation. I still haven't figured how to do this though but I would really appreciate it if you let me know if you found a solution, I would also share my solution if I find it. – M.kazem Akhgary Aug 25 '22 at 08:07
  • by the way I tried all of your solutions but they only moved the flipping issue somewhere else and did not actually solve the issue. but thanks for them anyways :) so far the simplest one i got was `FQuat::FindBetween(A_v, B_v)`. – M.kazem Akhgary Aug 25 '22 at 08:08
  • actually, I gave up on using a 3rd helper vector, that can only help with shifting the flip issue some where else. actually when you think about it, if there was a magic solution that had no twists, you would end up with contradictions if you rotate in different orders. so really the only solution would be to use the current rotation to figure out next closest rotation. – M.kazem Akhgary Aug 25 '22 at 10:24

0 Answers0