9
\$\begingroup\$

I rotate my game character to watch at the target using the following code:

        transform.rotation = Quaternion.Slerp(startQuaternion, lookQuaternion, turningNormalizer*turningSpeed/10f)

startQuaternion is the character's current rotation when a new target is given.

lookQuaternion is the direction the character should look at and it's set like this:

    destinationVector = currentWaypoint.transform.position - transform.position;
    lookQuaternion = Quaternion.LookRotation(destinationVector, Vector3.up);

turningNormalizer is just Time.deltaTime incremented and turningSpeed is a static value given in the editor.

The problem is that while the character turns as it should most of the time, it has problems when it has to do close to 180 degrees. Then it at times jitters and mirrors the rotation:

enter image description here

In this poorly drawn image the character(on the right) starts to turn towards the circle on the left. Instead of just turning either through left or right it starts this "mirror dance":

  1. It starts to rotate towards the new facing
  2. Then it suddenly snaps to the same angle but on other side and keeps rotating

It does this "mirroring" so long until it looks at the target. Is this a thing with quaternions, slerping/lerping or something else?

EDIT1: Apparently the issue does not arise from the rotating itself. The more likely issue is that the character moves toward the facing while it rotates. As limiting the angle, between facing and target, when the character is allowed to move reduces and at times eliminates the jittering/mirroring rotation.

This of course raises more questions, why cannot the character move and rotate at the same time without issues? Code used for the movement:

transform.Translate(Vector3.forward * runningSpeed/10f * Time.deltaTime);
\$\endgroup\$
1
  • \$\begingroup\$ Is 'lookQuaternion' being updated every frame? If so, then my presumption would be that some small inaccuracies are leading to a sort of jitter where the player 'overshoots' the look-rotation and so the new direction to look is just on the other side of 'directly behind'.... \$\endgroup\$ Oct 28, 2013 at 6:49

3 Answers 3

5
\$\begingroup\$

Each orientation in 3D space can be represented by 2 distinct unit quaternions, q and -q (component-wise negated q). For instance the orientation represented by the 3x3 identity matrix I can be represented by 2 quaternions:

 q:  { 0,  0,  0},  1
-q:  {-0, -0, -0}, -1

Both represent the same orientation in 3D space, their dot product is exactly -1, each of them lying in the other hemisphere, exactly on the opposite sides of the hypersphere.

The result you observe when the slerp takes the longer arc to rotate, is when slerping between 2 quaternions that do not lie in the same hemisphere. If that happens, just negate one of them before slerping them, then the slerp will take the shorter arc.

The dot product is an easy tool to find out whether that happens. If the dot product of both quaternions is less than 0, then they do not lie in the same hemisphere. So if the dot product of both of them is less than 0, just component-wise negate the other quaternion before slerping them.

\$\endgroup\$
7
  • \$\begingroup\$ I wonder if I understood you correctly as I tried it with this if(Quaternion.Dot (lookQuaternion, startQuaternion) < 0) { startQuaternion = Quaternion.Inverse(startQuaternion); } But it did not correct the issue. Sorry for the poor formatting, I can't seem to tame this comment section. \$\endgroup\$
    – Esa
    Oct 23, 2013 at 9:08
  • \$\begingroup\$ Almost, but not Inverse, that is a different operation than Negate. if(Quaternion.Dot (lookQuaternion, q) < 0) { q.x = -q.x; q.y = -q.y; q.z = -q.z; q.w = -q.w; } I do not use C#, but from the looks of the docu, it seems you could also use the Negate-Function directly. \$\endgroup\$ Oct 23, 2013 at 9:46
  • \$\begingroup\$ if(Quaternion.Dot (lookQuaternion, startQuaternion) < 0) { startQuaternion = Quaternion.Negate(startQuaternion); } \$\endgroup\$ Oct 23, 2013 at 9:48
  • \$\begingroup\$ I'm afraid these did not fix the issue. I have updated the question. \$\endgroup\$
    – Esa
    Oct 28, 2013 at 6:37
  • \$\begingroup\$ Yeah thats probably a mix of different bugs. Simplify your test setup, test one thing at a time, not translation and rotation at the same time. First make sure one works, then the other, and then both together. Focus only on rotation first, use some well known orientation values, like start at 170 degrees, moving down to zero, see where it goes wrong and post it here \$\endgroup\$ Oct 28, 2013 at 13:34
1
\$\begingroup\$

Well your problem is because those two vectors that form your quaternions make 180 degrees, so the question that should be asked when interpolating which arc is it supposed to take? the upper arc or the lower one ??

This is what basically causing the mirroring, each time you are interpolating it's taking the other way around while still maintaining the angle.

According to this link.

When theta is 180 degrees the result is undefined because there is no shortest direction to rotate, I don't think this situation is handled correctly yet, it would be better if we choose some arbitrary axis that is normal to qa or qb, I would appreciate any ideas for the best way to do this.

What you need to do is interpolating your starting quaternion with an intermediate quaternion (to determine which arc to take) and when reached use the intermediate quaternion to continue to the actual target quaternion.

\$\endgroup\$
1
\$\begingroup\$

The issue I suspect you're seeing has nothing to do with your code, but rather is a fundamental issue with interpolation along the surface of a sphere (in this case, for the viewDirection). Between (almost) any two points on a sphere there's a single great circle - for instance, arbitrarily fixing our 'start' point at the north pole, the great circle would be the meridian that the end point lies on. Interpolation from one point to the other moves along this great circle.

Now, for most points on a sphere, nearby points correspond to nearby great circles; for instance, the meridians that Los Angeles and Phoenix lie on are fairly close to each other. But when the destination point is the antipode of the original point — the south pole to the start point's north pole — there's no longer a single great circle through them both, but instead all the great circles through one go through the other. What's worse, this means that points near the south pole aren't 'most points'; two points near each other and both near the south pole can have wildly divergent meridians.

I suspect that what you're seeing is a manifestation of this instability in the meridian - or in other words, the interpolation arc. When the look target is directly behind the character, then things like the first-order inaccuracy in your 'Euler integration' of the character's position, and the way that changes the look direction frame-to-frame, are going to lead to these wildly different interpolation arcs from one frame to the next, and this will likely lead to the 'stagger' that you're seeing.

As far as what to do with it, the best thing that springs to mind is to not recompute the 'target' look direction every frame; instead, use the same one for a span of several frames, then compute a new one and use that for several frames, etc. If need be, you can even interpolate from one target to the next over the span of a few frames so that the direction of motion doesn't change too abruptly.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .