Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rotational Animation by Linear Combination of Transformation Matrices leads to Zoom-In-Zoom-Out

I have a 3x3 matrix (startMatrix), which represents the actual view of an image (translation, rotation and scale). Now I create a new matrix (endMatrix) with an identitymatrix, new x- and y-coordinates,new angle and new scale like:

endMatrix = translate(identityMatrix, -x, -y);  
endMatrix = rotate(endMatrix, angle);  
endMatrix = scale(endMatrix, scale);
endMatrix = translate(endMatrix,(screen.width/2)/scale,screen.height/2)/scale);

And the functions (standard stuff)

function scale(m,s) {
    var n = new Matrix([
        [s, 0, 0],
        [0, s, 0],
        [0, 0, s]
    ]);
    return n.multiply(m);
}
function rotate(m, theta) {
    var n = new Matrix([
        [Math.cos(theta), -Math.sin(theta), 0],
        [Math.sin(theta), Math.cos(theta), 0],
        [0, 0, 1]
    ]);
    return n.multiply(m);
}
function translate(m, x, y) {
    var n = new Matrix([
        [1, 0, x],
        [0, 1, y],
        [0, 0, 1]
    ]);
    return n.multiply(m);
}

After that i transform the image with css transform matrix3d (3d only for hardware acceleration). This transform is animated with requestAnimationFrame.

My startMatrix is for example

enter image description here

And The endMatrix

enter image description here

The linear combination looks like:

enter image description here

With t going from 0 to 1

The result of the linear combination of transformation matrices (the resulting image position) is correct, my problem now is: If the new angle is about 180 degree different from the actual angle, the endMatrix values change from positive to negative (or the other way around). This leads to an zoom-in zoom-out effect in the animation of the transformed image.

Is there a way to prevent this preferably with using one matrix for transforming?

like image 544
Alex Avatar asked Mar 10 '16 10:03

Alex


People also ask

What is the transformation matrix for translation transformation?

A type of transformation that occurs when a figure is moved from one location to another on the coordinate plane without changing its size, shape or orientation is a translation . Matrix addition can be used to find the coordinates of the translated figure.

What is transformation explain different transformations with the help of matrix?

A vector could be represented by an ordered pair (x,y) but it could also be represented by a column matrix: [xy] Polygons could also be represented in matrix form, we simply place all of the coordinates of the vertices into one matrix. This is called a vertex matrix.

How does a matrix transform space?

Transformation of a point (or vector) from one space to another involves a simple matrix–vector multiplication operation. Multiplying a point by a sequence of matrices can apply a sequence of transformations.

How do you combine translation and rotation matrix?

A rotation matrix and a translation matrix can be combined into a single matrix as follows, where the r's in the upper-left 3-by-3 matrix form a rotation and p, q and r form a translation vector. This matrix represents rotations followed by a translation.


2 Answers

There will be problems if you interpolate matrix values directly, for very small angles the inaccuraties can not be observed but in the longer run you will face problems. Even if you normalize the matrices greater angles will make the problems visually obvious.

2D rotation is quite simple, so you can do fine without the rotation matrix approach. The best method might be to use quaternions, but the are perhaps better suited for 3D transformations.

The steps to take are:

  1. Calculate the rotation, scale and transform values. If you already have these, you can skip this step. It might be easiest to keep these values separate for 2D matrix transforms.
  2. Then applly interpolation to those values
  3. Construct a new matrix based on the calculations

In the beginning of the animation you have to calculate values from step 1 once and then apply steps 2 and 3 at each frame.

Step 1: get the rotation, scale, transform

Assume the start Matrix is S and end Matrix is E.

The transformation values are simply the last columns of the, for example

var start_tx = S[0][2];
var start_ty = S[1][2];
var end_tx = E[0][2];
var end_ty = E[1][2];   

The Scale for non-skewing 2D matrix is simply length of either one the base vectors in the space the matrix is spanning, for example

// scale is just the length of the rotation matrixes vector
var startScale = Math.sqrt( S[0][0]*S[0][0] + S[1][0]*S[1][0]);
var endScale = Math.sqrt( E[0][0]*E[0][0] + E[1][0]*E[1][0]);

The hardest part is to get the rotation values for the matrix. The good thing is that it only has to be calculated once per interpolation.

The rotation angle for two 2D matrixes can be calculated based on the angle between the vectors the matrixes columns are creating. If there is no rotation, the first column has values (1,0) which represents the x-axis and second column has values (0,1) which represents the y-axis.

Generally the x-axis position for Matrix S is represented by

(S[0][0], S[0][1])

And the y-axis is pointing to direction

(S[1][0], S[1][1])

And the same for any 2D 3x3 matrix, like E.

Using this information you can determine the rotation angle between the two matrices using only standard vector mathematics - if we assume there is no skewing.

// normalize column vectors
var s00 = S[0][0]/ startScale;  // x-component
var s01 = S[0][1]/ startScale;  // y-component
var e00 = E[0][0]/ endScale;    // x-component
var e01 = E[0][1]/ endScale;    // y-component
// calculate dot product which is the cos of the angle
var dp_start   = s00*1 + s01*0;     // base rotation, dot prod against x-axis
var dp_between = s00*e00 + s01*e01; // between matrices
var startRotation  = Math.acos( dp_start );
var deltaRotation  = Math.acos( dp_between );

// if detect clockwise rotation, the y -comp of x-axis < 0
if(S[0][1]<0) startRotation = -1*startRotation;

// for the delta rotation calculate cross product
var cp_between = s00*e01 - s01*e00;
if(cp_between<0) deltaRotation = deltaRotation*-1;

var endRotation = startRotation + deltaRotation;

Here the startRotation is calculated just from acos of the first value of the matrix. However, the second columns first value, which is -sin(angle) is greater than zero then the matrix has been rotated clockwise and angle must be then negative. This has to be done because acos gives only positive values.

Another way to think of that would be consider cross product s00*e01 - s01*e00 where start position (s00,s01) is x axis, where s00 == 1 and s01 == 0 and the end (e00, e01) is the ( S[0][0], S[0][1] ) creating cross product

 1 * S[0][1] - 0 * S[0][0]

Which is S[0][1]. If that value is negative, the x-axis has been turned to clockwise direction.

For the endRotation we need the delta rotation from S to E. This can be calculated similarly from the dot product between the vectors the matrix is spanning. Similarly, we test for cross-product to see if the rotation direction is clockwise (negative angle).

Step 2: interpolate

During animation getting new values is trivial interpolation:

var scale = startScale + t*(endScale-startScale);
var rotation = startRotation + t*(endRotation-startRotation);
var tx = start_tx + t*(end_tx-start_tx);
var ty = start_ty + t*(end_ty-start_ty);

Step 3 construct the matrix

For each frame construct the final matrix you only have to place the values into the transformation matrix matrix

var cs = Math.cos(rotation);
var sn = Math.sin(rotation);
var matrix_values = [[scale*cs, -scale*sn, tx], [scale*sn, scale*cs, ty], [0,0,1]]

And then you have a 2D matrix which is easy to feed for any 3D hardware accelerator as well.

DISCLAIMER: some of the code has been tested, some of it has not been tested, so errors can most likely be found.

like image 172
Tero Tolonen Avatar answered Oct 18 '22 23:10

Tero Tolonen


When an animated rotation preserves scale, the points do not move along straight lines, but along circles. As a result, an intermediate matrix is not a linear combination of start and end matrices. The simplest way to overcome this is to calculate all transformations during each animation frame:

scale = startScale*(1-t)+endScale*t;
transformMatrix = translate(identityMatrix, -startX*(1-t)-endX*t, -startY*(1-t)-endY*t);  
transformMatrix = rotate(transformMatrix, startAngle*(1-t)+endAngle*t);  
transformMatrix = scale(transformMatrix, scale);
transformMatrix = translate(transformMatrix,screen.width/2/scale,screen.height/2/scale);
like image 43
mik Avatar answered Oct 19 '22 01:10

mik