1

Not only is using column-major vs row-major counter-intuitive, Apple's documentation on "Working with Matrices" further exacerbates the confusion by their examples of "constructing" a "Translate Matrix" and a "Rotation Matrix" in 2D.

Translate Matrix Per Apple's Documentation ()

Translate A translate matrix takes the following form:

1  0  0
0  1  0 
tx ty 1

The simd library provides constants for identity matrices (matrices with ones along the diagonal, and zeros elsewhere). The 3 x 3 Float identity matrix is matrix_identity_float3x3.

The following function returns a simd_float3x3 matrix using the specified tx and ty translate values by setting the elements in an identity matrix:

func makeTranslationMatrix(tx: Float, ty: Float) -> simd_float3x3 {
    var matrix = matrix_identity_float3x3

    matrix[0, 2] = tx
    matrix[1, 2] = ty

    return matrix 
}

My Issue with it

The line of code matrix[0, 2] = tx sets the value of the first column and the third row to tx. let translationMatrix = makeTranslationMatrix(tx: 1, ty: 3) and printing out the 2nd column print(translationMatrix.columns.2) will yield float3(0.0, 0.0, 1.0). I am very confused regarding why it is the last row that contains the translation values, rather than the column. This convention is not used when using SCNMatrix4MakeTranslation and creating a simd_float4x4 out of the SCNMatrix4 object.

var A = SCNMatrix4MakeTranslation(1,2,3)
var Asimd = simd_float4x4(A)

A.m41 // 1
A.m42 // 2
A.m43 // 3
A.m44 // 1

Asimd.columns.3 // float4(1.0, 2.0, 3.0, 1.0)

Both SCNMatrix4 and simd_float4x4 follow the column major naming convention. In the 2D example from Apple, it is the last row that contains the translation values, whereas with SCNMatrix4 and converting to simd_float4x4, it is the last column that contains the translation values. Apple's example seems to be doing the same with the Rotation Matrices as well.

What am I missing?

oneiros
  • 3,527
  • 12
  • 44
  • 71
  • _“and printing out the 2nd column `print(translationMatrix.columns.2)`”_ You aren't printing out the 2nd column here, you're printing the **3rd column**.  These are 0-based indexes (as is common in programming), so the `0` is the 1st; `1` is the 2nd, & `2` is the 3rd. The `float3(0.0, 0.0, 1.0)` you get as a result isn't related to the matrix values you set (in the 1st & 2nd columns)— it's the 3rd column, which still has the identity values. – Slipp D. Thompson Apr 09 '20 at 12:21
  • _(An identity 3x3 matrix has `1.0` diagonally in the `[0,0]`, `[1,1]`, & `[2,2]` spots, and `0.0` everywhere else.)_ – Slipp D. Thompson Apr 09 '20 at 12:28

1 Answers1

3

This can be confusing, yes.

The documentation you mentions makes the following computation:

let translatedVector = positionVector * translationMatrix

Note that the matrix is on the right side of the multiplication. You are probably used to the notation b = M * a but if you take the transpose you get b' = a' * M' which is what the sample does.

In SIMD there's no way to differentiate a vector from its transpose (b from b') and the library allows you to make the multiplication in both ways:

static simd_float3 SIMD_CFUNC simd_mul(simd_float3x3 __x, simd_float3 __y);
static simd_float3 SIMD_CFUNC simd_mul(simd_float3 __x,  simd_float3x3 __y) { return simd_mul(simd_transpose(__y), __x); }
mnuages
  • 13,049
  • 2
  • 23
  • 40
  • That explains a lot - however, I am not sure I understand the statement "In SIMD there's no way to differentiate a vector from its transpose (b from b')" - what exactly do you mean? I expect SIMD will multiply any matrices you specify, as long as the dimensions agree. Are you telling me that if initially the dimensions don't agree like a `4x4 times 1x4` matrix it will try to automatically transpose the second and turn it into a `4x4 times 4x1 = 4x1`? – oneiros Nov 23 '18 at 15:53
  • 1
    In SIMD there's no difference between `mat4x1` and `mat1x4`, they are both `vec4`. As a result the same `vec4` variable can be used on both sides of the multiplication, so you have to be extra cautious about the convention you use. – mnuages Nov 23 '18 at 16:18
  • Another way of looking at this is that in the first form `simd_mul(«matrix», «vector»)` the vector is interpreted as a column vector, while in the latter form `simd_mul(«vector», «matrix»)` the vector is interpreted as a row vector.  _Source: Apple's documentation comments at the top of ._ – Slipp D. Thompson Apr 14 '20 at 07:14