ARKit、SceneKitでは、transformプロパティを使って、3D変換(回転、拡大縮小、投影、移動)をすることがあります。このプロパティの設定を確認できるPlayground Codeを作りました。
<仕上がり>
XcodeのPlaygroud上でSceneKitの描画をしています。
transformプロパティとは
4x4行列で、SCNMatrix4型になっています。
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)・・・掛けても変化しない。他の行列を作るときのテンプレートとしてよく利用される。
\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)
\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)
\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)
\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)
\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)
\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に貼り付ければそのまま動作します。
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度の回転になります。
_ = {
let rad = Float.pi/12
let node = buildNode("Z軸に15度を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