5

Quaternion can describe not only rotation, but also an orientation, i.e. rotation from initial (zero) position.

I was wishing to model smooth rotation from one orientation to another. I calculated start orientation startOrientation and end orientation endOrientation and was wishing to describe intermediate orientations as startOrientation*(1-argument) + endOrientation*argument while argument changes from 0 to 1.

The code for monkey engine update function is follows:

@Override
    public void simpleUpdate(float tpf) {

        if( endOrientation != null ) {

            if( !started ) {
                started = true;
            }
            else {

                fraction += tpf * speed;
                argument = (float) ((1 - Math.cos(fraction * Math.PI)) / 2);

                orientation = startOrientation.mult(1-argument).add(endOrientation.mult(argument));
                //orientation = startOrientation.mult(1-fraction).add(endOrientation.mult(fraction));
                log.debug("tpf = {}, fraction = {}, argument = {}", tpf, fraction, argument);
                //log.debug("orientation = {}", orientation);

                rootNode.setLocalRotation(orientation);

                if( fraction >= 1 ) {

                    rootNode.setLocalRotation(endOrientation);
                    log.debug("Stopped rotating");

                    startOrientation = endOrientation = null;
                    fraction = 0;
                    started = false;
                }
            }
        }


    }

The cosine formula was expected to model smooth accelerating at the beginning and decelerating at the end.

The code works but not as expected: the smooth rotation starts and finishes long before fraction and argument values reach 1 and I don't understand, why.

Why the orientation value reaches endOrientation so fast?

Dims
  • 47,675
  • 117
  • 331
  • 600

1 Answers1

4

You have stated that in your case startOrientation was being modified. However; the following remains true

Interpolating between quaternions

The method slerp is included within the Quaternion class for this purpose: interpolating between two rotations.

Assuming we have two quaternions startOrientation and endOrientation and we want the point interpolation between them then we interpolate between then using the following code:

float interpolation=0.2f;
Quaternion result=new Quaternion();
result.slerp(startOrientation, endOrientation, interpolation);

Why your approach may be dangerous

Quaternions are somewhat complex internally and follow somewhat different mathematical rules to say vectors. You have called the multiply(float scalar) method on the quaternion. Internally this looks like this

public QuaternionD mult(float scalar) {
        return new QuaternionD(scalar * x, scalar * y, scalar * z, scalar * w);
}

So it just does a simple multiplication of all the elements. This explicitly does not return a rotation that is scalar times the size. In fact such a quaternion no longer represents a valid rotation at all since its no longer a unit quaternion. If you called normalise on this quaterion it would immediately undo the scaling. I'm sure Quaternion#multiply(float scalar) has some use but I am yet to find them.

It is also the case that "adding" quaternions does not combine them. In fact you multiply them. So combining q1 then q2 then q3 would be achieved as follows:

Quaternion q0 = q1.mult(q2).mult(q3);

The cheat sheet is incredibly useful for this

Formula vs slerp comparison

In your case your formula for interpolation is nearly but not quite correct. This shows a graph of yaw for interpolation between 2 quaternions using both methods

enter image description here

Richard Tingle
  • 16,906
  • 5
  • 52
  • 77
  • Apparently `slerp` behaves the same way as my function: rotation visually reaches final orientation much before `interpolation` reaches `1`. – Dims Jun 19 '14 at 21:27
  • Also I don't see `slerp` with your prototype: either it void, or takes 3 quaternions. – Dims Jun 19 '14 at 21:34
  • @Dims You are correct on the slerp method signature. Seems some on the JMonkey docs are out of date, edited. – Richard Tingle Jun 19 '14 at 21:40
  • @Dims With regard to rotating too quickly. What happens when it does reach 1. Has it rotated too far? Or does it stop abruptly at (for example) interpolation=0.8f – Richard Tingle Jun 19 '14 at 21:43
  • Finally I found a bug. My `startOrientation` was a reference which was changing in the process. After I clone it, the program started to work both with `slerp` and with my formula too. Perhaps `slerp` just does somethign similar. – Dims Jun 19 '14 at 21:57
  • So, remove the part "why your approach didn't work" and I will accept your answer :) – Dims Jun 19 '14 at 21:58
  • @Dims Glad you solved it. I've got the slerp source code up and it does something "similar", but not the same. In particular there is a lot of trig internal to the method. It would be interesting to plot the results of the two. In fact I might – Richard Tingle Jun 19 '14 at 22:03
  • @Dims As I suspected there is a subtle error that is being corrected within the slerp method. I've added a graph of the yaw when interpolating between two rotations that shows the slight difference – Richard Tingle Jun 19 '14 at 22:32