Alternative to LERP and SLERP (BURP)

I’m trying to come up with a better LERP or SLERP. In playing Orbiter (*) (space flight sim), when you use the autopilot to change orientations to face prograde, retrograde, normal or anti-normal, the ship spins in unnecessary directions. The rotations are simple 90/180 degree rotations. (** page 62) I thought that if you sort the rotations by the amount of change, the longest rotation would start first and the others start later making rotations on the x and z axis (I think y is up), in this case, unnecessary. For example, if you have the space shuttles belly facing the Earth (Note: the Enterprise pulls up to a planet with the port side facing the planet) and you want to change from prograde to Orbit Normal (+) to change the plane of your orbit, it’s a simple 90 deg rotation around only the y axis. But, LERP and SLERP start rolling on all axis.

Better Update of Rotational Properties (BURP) tries to fix that. It subtracts the prograde rotation from the current rotation, finds the shortest path (rather than just using whatever the difference is even if it takes the long way around), finds the longest of the x, y, z angles, and then uses the inverse of the time (1 – t) to calculate which axis to rotate and how much change to make. Note: it only does the heavy calculations on the first pass through and saves the results for later iterations.

Does anyone have a library that can give the prograde, retrograde, etc. orientations for the current orbit? All I can find is orbital velocity but, it doesn’t tell which direction it faces.

I’m no mathematician. Can anyone add the spherical part with sin and cos?

Also, can anyone explain how normalizing works in a quaternion, since the w component should be between 0 and 1 but, when you normalize it, it gets normalized with the other components. Or, are the other components clobbered and get clipped to 0 and 1 instead of going from –PI to PI.

From experience, I’ve learned that if I skip a detail, someone will ask me about it so, I’ll try to include everything here. See code below.

Also, I doubt anyone hangs out on the other sites I hang out on, but, I’m posting this or Orbiter and JMonkeyEngine and maybe some astrophysics site, too. I hope this is interesting to someone.

** BURPTest/Orbiter.pdf at master · Mipada/BURPTest · GitHub

On Github is the full Quaternion file. It is borrowed from JMonkeyEngine - with disclaimer attached!

You can play with it or use my test app, BURPTest on github.

If anyone is good at math and can add the sin and cos to make it SLERPish, that would be appreciated.


[code]

    //Basic Update of Rotation Properties

    //still need to convert to slerp (with sin/cos)

    float PI2f = (float)Math.PI * 2f;

    float PIf = (float)Math.PI;

    Quaternion diff = null;

    Vector4f r = new Vector4f();

    float max = 0.0f;

    public Quaternion burp(Quaternion q0, Quaternion q1, float t){

        return burp(q0, q1, t, false);

    }

    public Quaternion burp(Quaternion q0, Quaternion q1, float t, boolean initialize){

        boolean debug = true;

        //q0.normalizeLocal();//q0 = q0.normalize()

        //q1.normalizeLocal();//q1 = q1.normalize()

        if (q0.x == q1.x && q0.y == q1.y && q0.z == q1.z && q0.w == q1.w) {

            if (debug) System.out.println("BRUP.Ship.update() q0 = q1");

            this.set(q1);

            return this;

        }

 

        //set diff, max

        if (initialize){

            if (debug) System.out.println("BRUP.Ship.update() initializing");

            if (debug) System.out.println("q0=" + q0);

            if (debug) System.out.println("q1=" + q1);

            //not the result but the value of diff

            if (diff == null) diff = new Quaternion();

            diff = q1.subtract(q0);

            if (diff.x > PIf){

                diff.x = diff.x - PI2f;

            }

            else if (diff.x < -PIf){

                diff.x = diff.x - PI2f;

            }

 

            if (diff.y > PIf){

                diff.y = diff.y - PI2f;

            }

            else if (diff.y < -PIf){

                diff.y = diff.y - PI2f;

            }

 

            if (diff.z > PIf){

                diff.z = diff.z - PI2f;

            }

            else if (diff.z < -PIf){

                diff.z = diff.z - PI2f;

            }

 

            if (diff.w > PIf){

                diff.w = diff.w - PI2f;

            }

            else if (diff.w < -PIf){

                diff.w = diff.w - PI2f;

            }

            if (debug) System.out.println("diff=" + diff);

            //find max

            max = 0.0f;

            if (debug) System.out.println("abs of x=" + Math.abs(diff.getX()) + ", max=" + max);

            max = Math.abs(diff.x);

            if (debug) System.out.println("set max to x's max (" + max + ")");

 

            if (debug) System.out.println("abs of y=" + Math.abs(diff.getY()) + ", max=" + max);

            if (Math.abs(diff.y) > max){

                max = Math.abs(diff.y);

                if (debug) System.out.println("set max to y's max (" + max + ")");

            }

 

            if (debug) System.out.println("abs of z=" + Math.abs(diff.getZ()) + ", max=" + max);

            if (Math.abs(diff.z) > max){

                max = Math.abs(diff.z);

                if (debug) System.out.println("set max to z's max (" + max + ")");

            }

 

            //if (debug) System.out.println("abs of w=" + Math.abs(diff.getW()) + ", max=" + max);

            //if (Math.abs(diff.getW()) > max){

            //    max = Math.abs(diff.getW());

            //    System.out.println("set max to w's max (" + max + ")");

            //}

        }//end init

        r.x = q0.getX();

        r.y = q0.getY();

        r.z = q0.getZ();

        r.w = q0.getW();

        //BURP

        if ((float)Math.abs(diff.getX())/max > (1f - t)){//inverse of time

            if (debug) System.out.print("x, ");

            r.x = q0.getX() + t * diff.getX();

        }

        if ((float)Math.abs(diff.getY())/max > (1f - t)){

            if (debug) System.out.print("y, ");

            r.y = q0.getY() + t * diff.getY();

        }

        if ((float)Math.abs(diff.getZ())/max > (1f - t)){

            if (debug) System.out.print("z, ");

            r.z = q0.getZ() + t * diff.getZ();

        }

        //if ((float)Math.abs(diff.getW())/max > (1f - t)){

        //    System.out.print("w, ");

         //   r.w = q0.getW() + t * diff.getW();

        //}

        //noralize/clip

        if (r.x > PIf) r.x -= PI2f;

        else if (r.x < -PIf) r.x += PI2f;

 

        if (r.y > PIf) r.y -= PI2f;

        else if (r.y < -PIf) r.y += PI2f;

 

        if (r.z > PIf) r.z -= PI2f;

        else if (r.z < -PIf) r.z += PI2f;

 

        //if (r.w > PIf) r.w -= PI2f;

        //else if (r.w < -PIf) r.w += PI2f;

        //set

        set(r.x, r.y, r.z, r.w);

        return this;

    }

[\code]

Wait, do you think a quaternion is an angle+axis? Edit: Or even worse, do you think it’s some kind of Euler angle setup?

I skimmed your post and it seems that way. (And it’s not true at all, really.)

The way I think about quaternions is that it’s a visual representative of rotation. I don’t read it, I write to it. I don’t compare it because you can have many different outputs that represent the same rotation. It’s a bit of a black box.

I have my pitch, yaw, roll (x, y, z rotation) as radians. I set those values and update the quaternion with the changes to those values. If I want to compare, I convert the quaternion values to euler angles.

Normalized Quaternions are unique (except the negative version = positive version), Euler angles aren’t.

So comparing Quaternions is actually better than comparing Euler angles. There can be multiple sets of euler angles that lead to the same quaternion. And Quaternion.toAngles() is only going to give you one representation of that.

For small Quaternion differences then it is sometimes safe to compare the toAngles() of those two quaternions… but I personally wouldn’t trust its accuracy.

There are many ways to do steering, though… and Quaternion slerp()/nlerp() are not really steering primitives… they are animation primitives. They will give you a smooth quaternion interpolation but they will not at all (not even close on their best day) provide anything like attitude control outputs.

For those, the best bet is to use a direction vector of the second quaternion projected into the space of the first (for just yaw+pitch) and then calculate steering from that. (Edit: in case it wasn’t clear, for roll you also need a second vector: left or up, for example)

1 Like

If you want better interpolation of quaternions, I suggest you start by familiarizing yourself with prior work in the field. Here’s an introductory whitepaper that I found useful:
https://www.geometrictools.com/Documentation/Quaternions.pdf

Especially important is this little gem, tucked away on page 7:

 Although q1 and −q1 represent the same rotation, the values of Slerp(t;q0,q1) and
 Slerp(t;q0,−q1) are not the same.  It is customary to choose the sign σ on q1
 so that q0•(σq1)≥0 (the angle between q0 and σq1 is acute).
 This choice avoids extra spinning caused by the interpolated rotations
1 Like