指定した場所にジオメトリを複製する SCNReplicatorNode をつくってみた in SceneKit

ジオメトリを位置情報の配列をもとに複製したり、法線情報の配列から回転させるやつで 3DCG や動画編集などであるリプリケーター。
また、複製したジオメトリに対して SCNAction 設定を一括で設定することもできる。
作成した Swift File
SCNGeometory+Vertices.swift
SCNGeometory を extension で拡張して
vertices() でジオメトリの頂点情報を SCNVector3 の配列で返し、
normals() でジオメトリの頂点情報を SCNVector3 の配列で返す。
SCNReplicatorNode.swift
位置情報の配列を元に、複製したジオメトリを配置する SCNReplicatorNode と、 テスト用に作った、線、立方体、円で位置情報の配列を返す SCNPositionVectors が含まれている。
SCNReplicatorNode では「SCNVector3 を位置情報とした配列」と「複製するジオメトリ」が最低限必要となる。
SCNReplicatorNode
SCNReplicatorNode(
geometry: SCNGeometry, // 複製するジオメトリ
positions:[SCNVector3], // 複製する位置情報
normals:[SCNVector3], // 位置に対しての法線情報
upVector:SCNVector3, // 基準のなる上方向のベクトルを決める。デフォルト値: SCNVector3(0,1,0)
localFrontVector:SCNVector3 // 複製するジオメトリの正面方向の軸を決める。デフォルト値: SCNVector3(0,0,1)
)
normals、upVector、localFrontVector は省略可能。 upVector、localFrontVector は SCNNode の look(at:up:localFront:) の up と localFront にあたり、デフォルトでは +Z 軸方向が正面となる。
サンプル内容
ReplicatorSample1

// 複製する球のジオメトリ
let sphereGeometry = SCNSphere(radius: 0.1)
sphereGeometry.segmentCount = 8
// SCNReplicatorNode を使用して球を配置する
let replicatorNode = SCNReplicatorNode(
geometry: sphereGeometry,
positions: SCNPositionVectors.box(widthCount: 9, heightCount: 4, lengthCount: 9, margin: 1)
)
// SCNReplicatorNode のノードをシーンに追加する
scene.rootNode.addChildNode(replicatorNode)
SCNSphere で球のジオメトリで作成し、
SCNPositionVectors.box で、横 9 個、高さ 4 個、奥行き 9 個、間隔 1 毎に離れた立方体の位置情報に配置する。
ReplicatorSample2

// 複製する球のジオメトリ
let sphereGeometry = SCNSphere(radius: 0.01)
sphereGeometry.segmentCount = 8
// SCNReplicatorNode を使用して球を配置する
let replicatorNode = SCNReplicatorNode(
geometry: sphereGeometry,
positions: SCNPositionVectors.circle(divide: 120, radius: 4)
)
// SCNReplicatorNode から SCNAction を設定する
replicatorNode.replicatorAction(
SCNAction.repeatForever(
SCNAction.sequence([
.scale(to: 12, duration: 6),
.scale(to: 1, duration: 6)
])
),
delay: 0.1
)
// SCNReplicatorNode のノードをシーンに追加する
scene.rootNode.addChildNode(replicatorNode)
SCNSphere で球のジオメトリで作成し、
SCNPositionVectors.circle で円状に 120 個の位置を設定し先ほどの球を配置。
SCNReplicatorNode の replicatorAction 関数で SCNAction を設定すると各ノードにアクションが設定される。
replicatorAction の delay は各ノードのアクション開始までの待ち時間。
ReplicatorSample3

// 複製する位置情報を球のジオメトリ
let sphereGeometry = SCNSphere(radius: 4)
sphereGeometry.segmentCount = 16
// copy() 関数を使用する。これを使わないと設定変更した値が反映されない。
let deformedShpere:SCNGeometry = sphereGeometry.copy() as! SCNGeometry
// シーン(ship.scn)から複製するスペースシップのジオメトリノードを取得
let shipMesh = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
// SCNReplicatorNode を使用して球の位置にスペースシップを配置する
let replicatorNode = SCNReplicatorNode(
geometry: shipMesh.geometry!,
positions: deformedShpere.vertices(),
normals: deformedShpere.normals()
)
// スペースシップのスケールが大きいので変更する
replicatorNode.geometryScale(0.02,0.02,0.02)
// SCNReplicatorNode のノードをシーンに追加する
scene.rootNode.addChildNode(replicatorNode)
ビルトインジオメトリの SCNSphere の頂点情報を取得し、スペースシップの配置。
SCNSphere の頂点法線方向にスペースシップを正面に向かせたいため、
SCNReplicatorNode で SCNSphere の法線情報設定している。
また、ビルトインジオメトリに関しては copy() を使用しないと設定変更した形状が返らずデフォルトの値を返すので注意。
ReplicatorSample4
複製する位置の法線情報が設定されているが、ジオメトリ単位で変えたい場合

// シーン(ship.scn)から複製するスペースシップのジオメトリノードを取得
let shipMesh = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
// 複製する位置を設定
let refPositions = [
SCNVector3(-3, 0, 0),
SCNVector3(-1, 0, 0),
SCNVector3( 1, 0, 0),
SCNVector3( 3, 0, 0),
]
// 複製する際の法線情報を +Z 軸で設定
let refNormals = [
SCNVector3( 0, 0, 1),
SCNVector3( 0, 0, 1),
SCNVector3( 0, 0, 1),
SCNVector3( 0, 0, 1),
]
// SCNReplicatorNode を使用してスペースシップを配置する
let replicatorNode = SCNReplicatorNode(
geometry: shipMesh.geometry!,
positions: refPositions,
normals: refNormals,
upVector: SCNVector3(0, 0, 1),
localFrontVector: SCNVector3(0, 1, 0)
)
// スペースシップのスケールが大きいので変更する
replicatorNode.geometryScale(0.03,0.03,0.03)
// SCNReplicatorNode のノードをシーンに追加する
scene.rootNode.addChildNode(replicatorNode)
通常なら +Z 軸方向にスペースシップが並んでしまうが、
upVector と localFrontVector を変更することで向きを変えることができる。
画像ではジオメトリの +Y 軸方向が正面として変更されている。
ReplicatorSample5
スペースシップのジオメトリの頂点情報へスペースシップを貼り付けただけのもの

// シーン(ship.scn)から複製するスペースシップのジオメトリノードを取得
let shipMesh = scene.rootNode.childNode(withName: "shipMesh", recursively: true)!
// SCNReplicatorNode を使用してスペースシップを配置する
let replicatorNode = SCNReplicatorNode(
geometry: shipMesh.geometry!,
positions: shipMesh.geometry!.vertices(),
normals: shipMesh.geometry!.normals()
)
// スペースシップのスケールが大きいので変更する
replicatorNode.geometryScale(0.02,0.02,0.02)
replicatorNode.scale = SCNVector3(0.2, 0.2, 0.2)
// add replicator node in rootNode
scene.rootNode.addChildNode(replicatorNode)
まとめ
SceneKit の機能に、ジオメトリを簡単に複製させて配置させるものがないのでつくってみた。
けど、かなりの力技。
SCNReplicatorNode はただ addChildNode しているだけなので childNodes で調べていけば複製したものの設定は全て変えられるはず。