Question

In Actionscript 3, I have a 3D object that is rotating randomly about it's x, y, and z axes. I want to be able to stop it in place, then tween its rotation it to a specified rotation taking the shortest possible route. It seems quaternions are the right tool for the job, but I'm not sure how to tween it properly. I'm trying to tween axis-angle rotations, then convert it to a quaternion. Here's my test code so far.

var startV3DV:Vector.<Vector3D> = new <Vector3D>[positionV3D, new Vector3D(0,1,0,0), scaleV3D];
var endV3DV:Vector.<Vector3D> = new <Vector3D>[positionV3D, new Vector3D(0,1,0,Math.PI), scaleV3D];
var recomposeV3DV:Vector.<Vector3D> = new <Vector3D>[positionV3D, new Vector3D(0,0,0,0), scaleV3D];

TweenMax.to(startV3DV[1], 2, {x:endV3DV[1].x, y:endV3D[1].y, z:endV3D[1].z, w:endV3D[1].w, onUpdate:quat});

var quatV3D:Vector3D = new Vector3D();
function quat():void {
    quatV3D.x = currentV3D[1].x * Math.sin(currentV3D[1].w * .5);
    quatV3D.y = currentV3D[1].y * Math.sin(currentV3D[1].w * .5);
    quatV3D.z = currentV3D[1].z * Math.sin(currentV3D[1].w * .5);
    quatV3D.w = Math.cos(currentV3D[1].w * .5);
    recomposeV3DV[1] = quatV3D;
    myMatrix3D.recompose(recomposeV3DV,"quaternion");
}

It actually tweens the rotation of the object about its y axis for a little while, but when it hits PI/2, it bugs out and I get "ArgumentError: Error #2004: One of the parameters is invalid" on the recompose line, which I think means one of the quatV3D properties doesn't fit the quaternion formula. Any ideas on how to do this properly?

Was it helpful?

Solution

I can't reproduce it with the data you've provided. But it seems you're tweening on startV3DV while using a currentV3D Vector, which is not mentioned in your example code. It could contain invalid data such as 0 axis or 0 scale.

Furthermore, this is not how quaternions are supposed to be used for tweening. What you seem to be trying to do here is tween between two axis/angles representations and then generating a quaternion from that each time it's updated. You might as well reconstruct directly using the built-in "axisAngle" rotation mode. This probably won't do what you expect tho, because 1) it's not guaranteed to take the shortest angle, and 2) when the axis differs - all bets are off as to what the interpolation will look like ;)

What you should do is convert the start and end rotations to quaternions in the same way you're currently doing inside quat(), and interpolate between those instead. You'll still need to correct for the shortest path: if the 4-component dot product between the two quaternions < 0, you'll need to negate all components of one of them. Finally, you'll have to renormalize the quat (again 4-component-wise, rotation quaternions have to be unit-length).

If you want to stick to TweenMax, you could still do something like this (might contain some careless errors, but you should get the gist):

    t = 0;
    TweenMax.to(this, 2, {t:1, onUpdate:lerp});

    function lerp() : void
    {
        var x:Number, y:Number, z:Number, w:Number;
        var w1:Number = start.w, x1:Number = start.x, y1:Number = start.y, z1:Number = start.z;
        var w2:Number = end.w, end:Number = end.x, y2:Number = end.y, z2:Number = end.z;
        var len:Number;

        // shortest direction
        if (x1*x2 + y1*y2 + z1*z2 + w1*w2 < 0) {
            x2 = -x2;
            y2 = -y2;
            z2 = -z2;
            w2 = -w2;
        }

        x = x1 + t*(x2 - x1);
        y = y1 + t*(y2 - y1);
        z = z1 + t*(z2 - z1);
        w = w1 + t*(w2 - w1);

        len = 1.0/Math.sqrt(x*x + y*y + z*z + w*w);

        quatV3D.x = x*len;
        quatV3D.y = y*len;
        quatV3D.z = z*len;
        quatV3D.w = w*len;

        recomposeV3DV[1] = quatV3D;
        myMatrix3D.recompose(recomposeV3DV,"quaternion");
    }

You might also want to look into spherical linear interpolations ("slerp"), they have a constant angular velocity unlike linear interpolations. I'll be lazy and just direct you to the Away3D Quaternion class I wrote a few years ago: https://github.com/away3d/away3d-core-fp11/blob/008b1d83d3330281034b90ca7072722a9f486958/src/away3d/core/math/Quaternion.as

Hope this helps!

OTHER TIPS

It turns out that Matrix3D can already do LERP that uses quaternions:

var startM3D:Matrix3D = myObject.transform.matrix3D.clone();
var endM3D:Matrix3D = new Matrix3D(new <Number>[-1.2000000476837158,0,1.0490733615142744e-7,0,0,1.2000000476837158,0,0,-1.0490733615142744e-7,0,-1.2000000476837158,0,220,150,0,1]);
var currentM3D:Matrix3D = new Matrix3D();

var t:Number = 0;
TweenMax.to(this, 4, {t:1, onUpdate:lerp});

function lerp():void {
    currentM3D = Matrix3D.interpolate(startM3D, endM3D, t);
    myObject.transform.matrix3D = currentM3D;
}

The tween looks pretty good. I'll give your SLERP a try too though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top