SceneKitでカスタムジオメトリをつくる

SceneKitにはもともと立方体や球など基本となる形が用意されています。SceneKitを使う場合、これらの基本形状を利用するか、別の3Dモデリングツールで作成したモデルをインポートして利用する場合がほとんどです。

普通にSceneKitを使う分にはカスタムジオメトリをつくる動機はないのですが、ジオメトリについて深く理解したかったので、どのようにジオメトリを作成するのか調べてみました。例としてはSCNBoxと似た直方体を作成してみます。

カスタムジオメトリを作成するSCNGeometryのイニシャライザはSCNGeometry(sources: [SCNGeometrySource], elements: SCNGeometryElement]?)となっていてSCNGeometrySourceのArrayとSCNGeometryElementのArrayを渡して作成する仕様となっています。

SCNGeometrySourceはいくつかイニシャライザが用意されていますが、頂点(vertices)のみでも作成できるので今回は頂点のみ作成します。他に法線(normal)やテクスチャを作成することができます。

以下のコードで作成したカスタムジオメトリをSCNNodeに渡せば直方体を描画できます。

static func Box(width: CGFloat, height: CGFloat, length: CGFloat) -> SCNGeometry {
    let w = width / 2
    let h = height / 2
    let l = length / 2

    // 8つの頂点の座標を指定しています。
    let vertices = SCNGeometrySource(vertices: [
        // bottom
        SCNVector3(-w, -h, -l),
        SCNVector3(w, -h, -l),
        SCNVector3(w, -h, l),
        SCNVector3(-w, -h, l),
        // top
        SCNVector3(-w, h, -l),
        SCNVector3(w, h, -l),
        SCNVector3(w, h, l),
        SCNVector3(-w, h, l), 
    ])
    
    // 各頂点を結ぶ順番を指定します。8つの頂点を結ぶと12個の三角形で作成されます。
    // 0,1,3はインデックスが0,1,3の頂点を結んで三角形を描画することを意味します。
    // SceneKitでは反時計回りに頂点を結ぶ必要があります。
    let indices: [UInt32] = [
        // bottom
        0, 1, 3,
        3, 1, 2,

        // left
        0, 3, 4,
        4, 3, 7,

        // right
        1, 5, 2,
        2, 5, 6,

        // top
        4, 7, 5,
        5, 7, 6,

        // front
        3, 2, 7,
        7, 2, 6,

        // back
        0, 4, 1,
        1, 4, 5,
    ]

    // SCNGeometryElementは頂点(vertices)のインデックスとプリミティブのタイプを選択して作成します。
    let inds = SCNGeometryElement(indices: indices, primitiveType: .triangles)

    return SCNGeometry(sources: [vertices], elements: [inds])
}