ARKit、SceneKitのtransform(4x4行列）の使い方を簡単に確認できるPlayground Code

ARKit、SceneKitでは、transformプロパティを使って、3D変換（回転、拡大縮小、投影、移動）をすることがあります。このプロパティの設定を確認できるPlayground Codeを作りました。

＜仕上がり＞
XcodeのPlaygroud上でSceneKitの描画をしています。

<img width="400px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116010/386dad5e-ca36-1e79-4e33-f2a1d0c34057.png">

### transformプロパティとは

4x4行列で、SCNMatrix4型になっています。

```swift
node.transform = SCNMatrix4(
    m11:  0, m12:  0, m13:  0, m14:  0,
    m21:  0, m22:  1, m23:  0, m24:  0,
    m31:  0, m32:  0, m33:  1, m34:  0,
    m41:  0, m42:  0, m43:  0, m44:  1
)
```

### 4x4行列を用いた3D変換

単位行列（Identity）・・・掛けても変化しない。他の行列を作るときのテンプレートとしてよく利用される。

```math
\begin{pmatrix}
    m11:  1 & m12:  0 & m13:  0 & m14:  0 \\
    m21:  0 & m22:  1 & m23:  0 & m24:  0 \\
    m31:  0 & m32:  0 & m33:  1 & m34:  0 \\
    m41:  0 & m42:  0 & m43:  0 & m44:  1
\end{pmatrix}
```

移動（Translate）

```math
\begin{pmatrix}
    m11:  1 & m12:  0 & m13:  0 & m14:  0 \\
    m21:  0 & m22:  1 & m23:  0 & m24:  0 \\
    m31:  0 & m32:  0 & m33:  1 & m34:  0 \\
    m41: tx & m42: ty & m43: tz & m44:  1
\end{pmatrix}
```

拡大縮小（Scale）

```math
\begin{pmatrix}
    m11: sx & m12:  0 & m13:  0 & m14:  0 \\
    m21:  0 & m22: sy & m23:  0 & m24:  0 \\
    m31:  0 & m32:  0 & m33: sz & m34:  0 \\
    m41:  0 & m42:  0 & m43:  0 & m44:  1
\end{pmatrix}
```

X軸にそって回転（Rotate around X axis）

```math
\begin{pmatrix}
    m11:  1 & m12: 0 & m13: 0 & m14:  0 \\
    m21:  0 & m22:cosθ & m23: sinθ & m24:  0 \\
    m31:  0 & m32: -sinθ & m33:cosθ & m34:  0 \\
    m41:  0 & m42: 0 & m43: 0 & m44:  1
\end{pmatrix}
```

Y軸にそって回転（Rotate around Y axis）

```math
\begin{pmatrix}
    m11:  cosθ & m12:  0 & m13:  0 & m14: -sinθ \\
    m21:  0 & m22:  1 & m23:  0 & m24:  0 \\
    m31: sinθ & m32:  0 & m33: cosθ & m34:  0 \\
    m41:  0 & m42:  0 & m43:  0 & m44:  1
\end{pmatrix}
```

Z軸にそって回転（Rotate around Z axis）

```math
\begin{pmatrix}
    m11:cosθ & m12: sinθ & m13: 0 & m14:  0 \\
    m21: -sinθ & m22: cosθ & m23:  0 & m24:  0 \\
    m31:  0 & m32:  0 & m33:  1 & m34:  0 \\
    m41:  0 & m42:  0 & m43:  0 & m44:  1
\end{pmatrix}
```

### Playground Code

上の変換を適用したコードです。XCodeのPlaygroundに貼り付ければそのまま動作します。

```swift:Playground
import SceneKit
import QuartzCore
import XCPlayground
import PlaygroundSupport

// 行列内容を見やすく表示する関数
// 行ベクトル表示
func printMatrixByRow(_ matrix: SCNMatrix4) {
    print("---")
    print("m11: \(matrix.m11) m21: \(matrix.m21) m31: \(matrix.m31) m41: \(matrix.m41)")
    print("m12: \(matrix.m12) m22: \(matrix.m22) m32: \(matrix.m32) m42: \(matrix.m42)")
    print("m13: \(matrix.m13) m23: \(matrix.m23) m33: \(matrix.m33) m43: \(matrix.m43)")
    print("m14: \(matrix.m14) m24: \(matrix.m24) m34: \(matrix.m34) m44: \(matrix.m44)")
}

// 列ベクトル表示
func printMatrix2ByCol(_ matrix: SCNMatrix4) {
    print("---")
    print("m11: \(matrix.m11), m12: \(matrix.m12), m13: \(matrix.m13), m14: \(matrix.m14),")
    print("m21: \(matrix.m21), m22: \(matrix.m22), m23: \(matrix.m23), m24: \(matrix.m24),")
    print("m31: \(matrix.m31), m32: \(matrix.m32), m33: \(matrix.m33), m34: \(matrix.m34),")
    print("m41: \(matrix.m41), m42: \(matrix.m42), m43: \(matrix.m43), m44: \(matrix.m44)")
}

// ビューを用意
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
var scene = SCNScene()
sceneView.scene = scene

PlaygroundPage.current.liveView = sceneView

sceneView.autoenablesDefaultLighting = true

// カメラを設置
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0.6, y: 1.2, z: 3)
scene.rootNode.addChildNode(cameraNode)

// 基本となる図形を用意
func buildNode(_ text: String) -> SCNNode {
    let text = SCNText(string: text, extrusionDepth: 0.2)
    text.font = UIFont.systemFont(ofSize: 0.3)
    text.firstMaterial?.diffuse.contents  = UIColor.red
    return SCNNode(geometry: text)
}


// ここから様々な位置に表示

// 変換なし
_ = {
    let node = buildNode("変換なし")
    printMatrixByRow(node.transform)
    scene.rootNode.addChildNode(node)
}()


// positionを使って移動
_ = {
    let node = buildNode("拡大して、右に1メートル、上に2メートル、奥に3メートル")
    node.position = SCNVector3(1, 2, -3)
    node.scale = SCNVector3(2, 2, 2)
    printMatrixByRow(node.transform)
    scene.rootNode.addChildNode(node)
    printMatrix2ByCol(node.transform)
}()

// position, rotationを使って移動・回転
_ = {
    let node = buildNode("左に1m、奥に3m、Z軸方向に45度回転")
    node.position = SCNVector3(-1, 0, -3)
    node.rotation = SCNVector4(0, 0, 1, Float.pi/4)
    printMatrixByRow(node.transform)
    scene.rootNode.addChildNode(node)
}()

// transformを使って移動
_ = {
    let node = buildNode("拡大して、右に1メートル、下に2メートル、奥に3メートル")
    node.transform = SCNMatrix4(
        m11: 2.0, m12:  0.0, m13:  0.0, m14: 0.0,
        m21: 0.0, m22:  2.0, m23:  0.0, m24: 0.0,
        m31: 0.0, m32:  0.0, m33:  2.0, m34: 0.0,
        m41: 1.0, m42: -2.0, m43: -3.0, m44: 1.0
    )
    printMatrixByRow(node.transform)
    scene.rootNode.addChildNode(node)
}()

// transformを使って移動・回転
_ = {
    
    let rad = Float.pi/4
    
    let node = buildNode("左に1m、下に2m、奥に3m,Z軸に45度回転")
    node.transform = SCNMatrix4(
        m11:  cos(rad), m12: sin(rad), m13:  0.0, m14: 0.0,
        m21: -sin(rad), m22: cos(rad), m23:  0.0, m24: 0.0,
        m31:       0.0, m32:      0.0, m33:  1.0, m34: 0.0,
        m41:      -1.0, m42:     -2.0, m43: -3.0, m44: 1.0
    )
    printMatrixByRow(node.transform)
    scene.rootNode.addChildNode(node)
}()
```

### 番外編

transformを掛け合わせることで、変換を合成することができます。
これは、例えば15度の回転を3回掛け合わせています。結果は45度の回転になります。

```swift:Playground
_ = {

    let rad = Float.pi/12

    let node = buildNode("Z軸に１５度を3回回転(=45度の回転）")
    let t1 = SCNMatrix4(
        m11:  cos(rad), m12: sin(rad), m13:  0.0, m14: 0.0,
        m21: -sin(rad), m22: cos(rad), m23:  0.0, m24: 0.0,
        m31:       0.0, m32:      0.0, m33:  1.0, m34: 0.0,
        m41:       0.0, m42:      0.0, m43:  0.0, m44: 1.0
    )
    let t2 = SCNMatrix4(
        m11:  cos(rad), m12: sin(rad), m13:  0.0, m14: 0.0,
        m21: -sin(rad), m22: cos(rad), m23:  0.0, m24: 0.0,
        m31:       0.0, m32:      0.0, m33:  1.0, m34: 0.0,
        m41:       0.0, m42:      0.0, m43:  0.0, m44: 1.0
    )
    let t3 = SCNMatrix4(
        m11:  cos(rad), m12: sin(rad), m13:  0.0, m14: 0.0,
        m21: -sin(rad), m22: cos(rad), m23:  0.0, m24: 0.0,
        m31:       0.0, m32:      0.0, m33:  1.0, m34: 0.0,
        m41:       0.0, m42:      0.0, m43:  0.0, m44: 1.0
    )
    node.transform = SCNMatrix4Mult(SCNMatrix4Mult(t1, t2),t3)
    printMatrixByRow(node.transform)
    scene.rootNode.addChildNode(node)
}()
```

### 最後に
NoteではiOS開発、AR、機械学習などについて定期的に発信しています。
https://note.com/tokyoyoshida

Twitterでも発信しています。
https://twitter.com/jugemjugemjugem