Prizoff
Prizoff

Reputation: 4575

Why does android.opengl.Matrix.translateM() produce unexpected result?

From the sources of android.opengl.Matrix.translateM(float[] m, int mOffset, float x, float y, float z):

/**
 * Translates matrix m by x, y, and z in place.
 * @param m matrix
 * @param mOffset index into m where the matrix starts
 * @param x translation factor x
 * @param y translation factor y
 * @param z translation factor z
 */
public static void translateM(
        float[] m, int mOffset,
        float x, float y, float z) {
    for (int i=0 ; i<4 ; i++) {
        int mi = mOffset + i;
        m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
    }
}

The question is about this line (or the entire for-loop):

m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;

Why x, y and z components of translate parameters are multiplied to the source matrix components here? Shouldn't it be simple this (this lines replace the entire for-cycle):

m[12 + mi] += x;
m[13 + mi] += y;
m[14 + mi] += z;

Background of the problem:

I am doing some 2d game in OpenGL ES 2.0. I whant to scale and move some object there. While I simple move it:

Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, x, y, 0);

all goes fine. As soon as I do scaling before moving - at this time moving translations:

Matrix.setIdentityM(mModelMatrix, 0);
Matrix.scaleM(mModelMatrix, 0, xScaleFactor, yScaleFactor, 1);
Matrix.translateM(mModelMatrix, 0, x, y, 0);

are in fact multiplied by scaling :(

Upvotes: 1

Views: 4717

Answers (2)

hyperpallium
hyperpallium

Reputation: 589

translateM simulates matrix multiplication of m X T, where T is a translation matrix.

A translation matrix is like an identity matrix (diagonal 1's) with a last column of x,y,z,1 for the translation. (The 1 is for homogenous coordinates which I won't go into).

Assuming you know matrix multiplication (where each row of the first operand is pair-wise mutliplied by each column of the second operand), it takes short-cuts by exploiting the specifics of a translation matrix T:

It doesn't actually create the translation matrix T, but simulates the multiplication m x T. Most of the values don't change (because of the part like an identity matrix), so it need only calculate the values that do change (involving the last column of T).

Each loop iteration pair-wise multiplies one row of m by the last column of the translation matrix (which is x,y,z,1). The line of code would be (with i being the row of m and column of T):

m[12+i] = m[0+i]*x + m[4+i]*y + m[8+i]*z + m[12+i]*1f;

but they simplify it with the += operator; use mi for offset; and omit 0+.

Elements that don't change can just be left there, because this version of translateM is "in-place",.

Bear in mind it uses matrices in column-major form, with indices:

0  4  8  12
1  5  9  13
2  6  10 14
3  7  11 15

As you step through the array, you go down through each element in a column, and at the end of it go across to the next column, etc. So the element at row i and column j is at m[i + 4*j]. That is, going down is +1 and going across is +4. thr next (The other way is row-major, where you go across a row, the down to the next row etc. More intuitive for printing, but not what android.opengl.Matrix uses.)


Your scaleM then translateM is equivalent to S x T (scaling matrix S, translation matrix T). The effect is right-to-left (i.e. backwards), of the translation first and then the scaling. So, the translation also gets scaled.

As the accepted answer says, doing translateM then scaleM (i.e. T x S) has the effect of scaling then translating (so the translation isn't scaled).

One way to think about this is that this matrix will eventually be multiplied by a column vector v: (T x S) x v. Beginning differently, with S x v = v' scaling v, and then translating that result with T x v', we could combine them as T x (S x v). It seems natural that the scaling must be first. Perhaps surprisingly, because matrix multiplication is associative, it is the same as (T x S) x v. If we ignore the last x v (which is done on the GPU), we have T x S, which means "scale, and then translate". The matrix closest to the right is done first.

BTW The associativity of matrix multiplication is like java string concatenation, where the order of evaluation doesn't matter, where (a+b)+c = a+(b+c) (though the order of operands does matter, e.g. a+b+c != c+b+a - neither is commutative). This means we can whichever part first that suits us.


BTW: there are several layers here: matrix multiplication, the effect of ordering, homogeneous coordinates, translation matrix, effect of multiplication ordering, column-major form, the above multiplication short-cut, +=, and mOffset,mi` (for many matrices in one array).

Much of programming can be understood as you go along, but for this, it's better to get on top of each layer in turn.

Upvotes: 1

Andon M. Coleman
Andon M. Coleman

Reputation: 43319

OpenGL is column-major, the proper order for Scaling, Rotation and Translation is actually Translation * Rotation * Scaling. In D3D and any row-major matrix library, Scale * Rotate * Translate would be correct. You have to think about things from right-to-left when you use column-major matrices.

Alternatively, you could transpose each matrix before multiplication - it is usually simpler just to follow the canonical order for column-major matrix multiplication though. Note that this applies to things like Position * Model * View * Projection (D3D / row-major) as well, in GL (column-major) the proper order is Projection * View * Model * Position.

Upvotes: 3

Related Questions