4

There seems to be at least half a dozen matrix libraries in the Apple system. One of them is the simd library with types that work the same in CPU and GPU code.

import simd
let mat = float3x3(...)
let vec = float3(...)
mat * vec

I'm having trouble finding documentation for it. Unlike most things it does not show up in Xcode's documentation browser. I know that a different library (GLKit) has matrix types that have functions for building rotation matrixes. For example,

GLKMatrix3MakeXRotation(radians)
GLKMatrix3RotateY(mat, radians)

Are there similar functions for the simd matrix types?

Rob N
  • 15,024
  • 17
  • 92
  • 165

2 Answers2

8

You can go through simd_quat. Quaternions have a simple connection to the angle-axis representation. The SIMD library can construct a quaternion from angle-axis, and there's also a function to construct a float3x3 from a quaternion. (This is C++, but same idea should work in Swift).

inline float3x3 MakeRotation(float radians, float x, float y, float z) {
  simd_quatf quat = simd_quaternion(radians, (simd_float3){x, y, z});
  return simd_matrix3x3(quat);
}
Baxissimo
  • 2,629
  • 2
  • 25
  • 23
  • there are no quaternions in Metal, which is what the question is about. simd_quatf and others do not exist. – johnbakers Oct 14 '22 at 13:09
5

There are not currently utility functions for creating such matrices in simd.framework, Metal, or MetalKit. However, you can use GLKit's matrix functions and convert the resulting GLKMatrix4s into float4x4s before, for example, copying them into a Metal buffer for use in a shader.

A GLKMatrix4 is just a union containing an array of 16 floats, stored in column-major order.

Therefore, we can write an extension on float4x4 that allows initializing a simd matrix with a GLKit matrix:

extension float4x4 {
    init(matrix: GLKMatrix4) {
        self.init(columns: (float4(x: matrix.m00, y: matrix.m01, z: matrix.m02, w: matrix.m03),
                            float4(x: matrix.m10, y: matrix.m11, z: matrix.m12, w: matrix.m13),
                            float4(x: matrix.m20, y: matrix.m21, z: matrix.m22, w: matrix.m23),
                            float4(x: matrix.m30, y: matrix.m31, z: matrix.m32, w: matrix.m33)))
    }
}

I verified that the resulting matrix matched my expectations by creating a GLKit matrix that represents a 45-degree rotation counterclockwise about the +Z axis, and ensuring that it does, in fact, rotate the unit vector <1, 0, 0> onto the unit vector <sqrt(2)/2, sqrt(2)/2, 0>:

let rotation = GLKMatrix4MakeZRotation(.pi / 4)
let simdRotation = float4x4(matrix: rotation)
let v = float4(1, 0, 0, 0)
let vp = simdRotation * v
print("\(vp)")


> float4(0.707107, 0.707107, 0.0, 0.0)

Note that I'm abiding the convention here that matrix-vector multiplication treats the vector as a column vector and places the matrix on the left, which is the most common convention in current use.

There is one caveat you should be aware of with respect to GLKit and Metal's clip space. You can read about the issue, and how to correct for it, here.

warrenm
  • 31,094
  • 6
  • 92
  • 116
  • Thanks! Sounds like `GLKMatrix4` (or 3) has the same memory layout as `float4x4` (or 3x3), so if you need to you can reinterpret the memory as one or the other. ? I see that since Swift lets me define operators like * and +, the GLK matrixes become just about as convenient to use as the simd ones. – Rob N Apr 04 '18 at 19:32
  • 1
    Right. I provided the initializer as an example of how to "safely" convert one to the other, but since they have identical layout, you could just as readily reinterpret the contents of a `GLKMatrix4` as a `float4x4`. Not so with `GLKMatrix3`, which has 9 packed float members, while `float3x3` has an array of three `float3`, each of which has a stride of **16 bytes** rather than 12 bytes for simd alignment reasons. – warrenm Apr 04 '18 at 20:02