ARKitの紹介でよく見る、空間に絵を書くデモ。どの様な仕組みで書いているのか調べてみました。
実装する内容
タッチ中の座標に、renderer(_:updateAtTime:)の更新を利用して
球体ノードを連続して追加するとお絵かきっぽくなります。
1
2
|
optional func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval)
|
※公式リファレンス
https://developer.apple.com/documentation/scenekit/scnscenerendererdelegate/1522937-renderer
今回は、カスタムジオメトリは作成せずに、単に球体のノードを追加していく事で
お絵かきを実現していきます。
以下に実装の流れを記載します。
※ARKitの基本的な実装は、今回の説明からは省略しています。
タッチの座標と、状態を保持する変数を用意
タッチの座標とタッチの状態を保持するグローバル変数を用意します。
1
2
|
var touchedLocation: CGPoint = .zero
var isTouching = false
|
UIResponderを利用し、タッチ情報を取得
touchesBegan
タッチ状態をtrueに変更、タッチ座標を取得しtouchedLocation変数に格納
1
2
3
4
5
6
7
8
9
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = true
guard let location = touches.first?.location(in: nil) else {
return
}
touchedLocation = location
}
|
touchesMoved
タッチの動きに合わせて、座標を取得を続ける
1
2
3
4
5
6
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let location = touches.first?.location(in: nil) else {
return
}
touchedLocation = location
}
|
touchesEnded
タッチ終了(指が離れる)の検知で、タッチ状態をfalseに変更
1
2
3
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = false
}
|
renderer(_: update At Time:)で球体ノードを追加
renderer(_:updateAtTime:)は、フレーム更新毎する度に呼ばれます。
isTouchingがtrue(タッチされている状態)の間は、ここで球体のノードを追加します。
端末負荷を下げる為に、一度作成したノードはコピーして再利用しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var existentNode: SCNNode?
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard isTouching else {
return
}
let ballNode: SCNNode
//既にノードが作成したら、clone()でコピーして再利用する
if let node = existentNode {
ballNode = node.clone()
} else {
ballNode = createBallNode()
existentNode = ballNode
}
//スクリーン座標から、ワールド座標に変換する
let wordPostion = sceneView.unprojectPoint(SCNVector3(touchedLocation.x, touchedLocation.y, 0.995))
ballNode.position = wordPostion
sceneView.scene.rootNode.addChildNode(ballNode)
}
|
実行
お絵かきっぽくはなりますが、球体ノードの連続だとあまりきれいに描けないですね。
次回はカスタムジオメトリを作成して実装してみようと思います。
コード全文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
import UIKit
import ARKit
class ViewController: UIViewController {
@IBOutlet var sceneView: ARSCNView!
var touchedLocation: CGPoint = .zero
var isTouching = false
var existentNode: SCNNode?
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
sceneView.scene = SCNScene()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
configuration.isLightEstimationEnabled = true
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = true
guard let location = touches.first?.location(in: nil) else {
return
}
touchedLocation = location
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let location = touches.first?.location(in: nil) else {
return
}
touchedLocation = location
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isTouching = false
}
func createBallNode()-> SCNNode {
let ball = SCNSphere(radius: 0.003)
ball.firstMaterial?.diffuse.contents = UIColor.blue
return SCNNode(geometry: ball)
}
}
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard isTouching else {
return
}
let ballNode: SCNNode
//既にノードが作成したら、clone()でコピーして再利用する
if let node = existentNode {
ballNode = node.clone()
} else {
ballNode = createBallNode()
existentNode = ballNode
}
//スクリーン座標から、ワールド座標に変換する
let wordPostion = sceneView.unprojectPoint(SCNVector3(touchedLocation.x, touchedLocation.y, 0.995))
ballNode.position = wordPostion
sceneView.scene.rootNode.addChildNode(ballNode)
}
}
|
コメント