ARKitを使って空間に線を描く方法（初級編）

ARKitを使って線を書けるアプリで有名なのが[Just a line](https://itunes.apple.com/app/id1367242427?mt=8)、[World Brush](https://itunes.apple.com/app/id1277410449?mt=8)など。その他にもFacebookのカメラでも空間に落書きできたり、落書きアプリというのはいくつかあります。

また僕の所属するGraffityでも空間に落書きするアプリを作ってきました。

今記事では、**どういった原理で空間に落書きしているのか**解説します。

# 一番カンタンな線を書く方法

「カメラから一定の距離に球を置き、その連続で線を作る」という方法がもっとも簡単です。

|連続で点を打つ|点を打つスパンを短く|
|:-:|:-:|
|![GIFイメージ-F6AAB9EDF2C6-1.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/ec5878f8-0e22-38e2-884f-d73d22711756.gif)|![GIFイメージ-628FB6E0CF25-1.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/22634bed-4a61-a6cb-7ee7-83e9b133c8d7.gif)|

```swift
// タップした状態で指を動かすと呼ばれるメソッド
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let point = touches.first?.location(in: sceneView) else {
        return
    }
    let point3D = sceneView.unprojectPoint(SCNVector3(point.x, point.y, 0.997))
    
    // 球のNodeを作る
    let ball = SCNSphere(radius: 0.005)
    ball.firstMaterial?.diffuse.contents = UIColor.red
    let node = SCNNode(geometry: ball)
    node.position = point3D
    sceneView.scene.rootNode.addChildNode(node)
}
```
https://github.com/kboy-silvergym/ARKit-Emperor/blob/master/ARKit-Emperor/View/Doodle/DoodleViewController.swift

# フレームの更新を利用する方法
![fps](https://upload.wikimedia.org/wikipedia/commons/2/29/OvercrankingTimeline.png)

先程の方法だと、指の動きをトラックしているだけなので、デバイスを動かしたときに動いたとみなされず、お絵かきの幅が広がりません。

**以下のように、踊りながらお絵描きができないわけです。**

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">芸術っぽさを感じる <a href="https://t.co/Qusha5OtpH">pic.twitter.com/Qusha5OtpH</a></p>&mdash; K-BOY@ARエンジニア (@kboy_silvergym) <a href="https://twitter.com/kboy_silvergym/status/1071757583222861824?ref_src=twsrc%5Etfw">2018年12月9日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


そこで、フレームの更新を利用する方法があります。タッチ中はフラグを立てておいて（必要に応じてタッチのポジションを先のtouchesMovedメソッドで取得し）、**フレームが更新するたびに呼ばれるARSCNViewDelegateのrenderer(:updateTime)の中で球を置いていくパターン**です。

ちなみにARKitでは、デフォルトでは60fpsなので、１秒間に60回フレームが更新されてカメラが動画として見えます。つまり、この方法では１秒間に60回球を置いておくことになります。

```swift
private var pointTouching: CGPoint = .zero

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    isTouching = true

    guard let location = touches.first?.location(in: nil) else {
        return
    }
    pointTouching = location
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let location = touches.first?.location(in: nil) else {
        return
    }
    pointTouching = location
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    isTouching = false
}

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    guard isTouching else {return}
    
    // 球のNodeを作って置く
    let point3D = sceneView.unprojectPoint(SCNVector3(pointTouching.x, pointTouching.y, 0.997))

    // 球のNodeを作る
    let ball = SCNSphere(radius: 0.005)
    ball.firstMaterial?.diffuse.contents = UIColor.red
    let node = SCNNode(geometry: ball)
    node.position = point3D
    sceneView.scene.rootNode.addChildNode(node)
}
```

# 球を連続で置くことに対する課題

ここまで述べたように、「**球を連続に置く**」ことで線が出来上がります。しかしSCNNodeを短い時間に連続で置きまくるととても重くなります。そして、そんなにきれいな線じゃないですよね。

ここで[3DBrush](https://itunes.apple.com/app/id1286537390?mt=8)というアプリを見てみましょう。

![GIFイメージ-D660F2DDC91F-1.gif](https://qiita-image-store.s3.amazonaws.com/0/111366/7fffb3b9-6443-d83d-e03b-0f0b4ac05f3d.gif)

3DBrushは球の連続で線を書くのではなく、**１本の線単位の曲線のジオメトリを持つSCNNodeを作って置いている**ように見えます。よりメモリ効率の良い、より綺麗な線を描画するためにはこのアプローチが良さそうです。

では、どのようにこのアプローチをしていくのでしょうか？

こちらは、また明日の記事で[上級編](https://qiita.com/k-boy/items/f18d034e117cc66be4ef)として書きたいと思います。

では！