LoginSignup
5
4

More than 5 years have passed since last update.

ARKit2.0でPresistent ARを実装してみる

Posted at

本家のサンプルコードがMultiuser ARしか無いのでPresistent ARを試してみた。

準備

iOS12、Xcode12

ARKit2.0はiOS12以降が対応しているので、iOS12の開発環境が必要になる。
2018年7月現在、iOS12とXcode12はbeta版の公開のみなので、自己責任でインストールする必要がある。

言語はswift4(ちなみに初心者)。

Augmented Reality Appから始める

Application > Augmented Reality Appを選択。

タップした位置に箱を表示する

viewDidLoadを修正。

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set the view's delegate
        sceneView.delegate = self
        // 特徴点の表示
        sceneView.debugOptions = ARSCNDebugOptions.showFeaturePoints

        // Create a new scene
        let scene = SCNScene()

        // Set the scene to the view
        sceneView.scene = scene

        // タップジェスチャーの登録
        let gesture = UITapGestureRecognizer(target: self, action: #selector(tapView))
        sceneView.addGestureRecognizer(gesture)
    }

tapViewの実装。タップした位置にアンカーを追加。

    @objc func tapView(sender: UITapGestureRecognizer) {
        guard let hitTestResult = sceneView
            .hitTest(sender.location(in: sceneView), types: [.existingPlaneUsingGeometry, .estimatedHorizontalPlane])
            .first
            else { return }
        let anchor = ARAnchor(name: "box", transform: hitTestResult.worldTransform)
        sceneView.session.add(anchor: anchor)
    }

sessionにAnchorを追加した時の動作を記述。タップ時や読出時に、箱が追加される。

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        if let name = anchor.name, name.hasPrefix("box") {
            let geometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
            let material = SCNMaterial()
            material.diffuse.contents = UIColor.darkGray
            geometry.materials = [material]

            let addnode = SCNNode(geometry: geometry)

            node.addChildNode(addnode)
        }
    }

ここまでで、タップした位置に箱が表示されるアプリができている。

ARWorldMapの保持、初期化、読出をする

ローカルに保存する保存先としてsaveURLを定義しておく。

...
class ViewController: UIViewController, ARSCNViewDelegate {
    // 拡張子は適当
    let saveURL = "arworldmap.dat"
...

Main.storyboardでARSCNView上に「SAVE」「INIT」「LOAD」ボタンを配置し、touchDownイベントをViewControllerの@IBAction関数とする。

    @IBAction func saveTouchDown(_ sender: Any) {
    }

    @IBAction func initTouchDown(_ sender: Any) {
    }

    @IBAction func loadTouchDown(_ sender: Any) {
    }

それぞれボタン押下時の動作を実装していく。

    @IBAction func saveTouchDown(_ sender: Any) {
        sceneView.session.getCurrentWorldMap { worldMap, error in
            guard let map = worldMap
                else { print("Error: \(error!.localizedDescription)"); return }
            guard let data = try? NSKeyedArchiver.archivedData(withRootObject: map, requiringSecureCoding: true)
                else { fatalError("can't encode map") }

            if let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first {

                let path_file_name = dir.appendingPathComponent( self.saveURL )

                guard ((try? data.write(to: path_file_name)) != nil) else { return }
            }

            self.displayAlert(message: "保存しました")
        }
    }

    @IBAction func initTouchDown(_ sender: Any) {
        sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
            node.removeFromParentNode()
        }

        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal
        sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

        self.displayAlert(message: "初期化しました")
    }

    @IBAction func loadTouchDown(_ sender: Any) {
        var data: Data? = nil
        if let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first {

            let path_file_name = dir.appendingPathComponent( self.saveURL )

            do {
                try data = Data(contentsOf: path_file_name)
            } catch {
                return
            }
        }

        guard let worldMap = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data!) else { return }

        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal
        configuration.initialWorldMap = worldMap
        sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

        self.displayAlert(message: "読み込みました")
    }

メッセージをアラート表示するdisplayAlertの実装。

    func displayAlert(message: String){
        let alert = UIAlertController(title: "アラート", message: message, preferredStyle: UIAlertController.Style.alert)
        let okayButton = UIAlertAction(title: "ok", style: UIAlertAction.Style.cancel, handler: nil)
        alert.addAction(okayButton)

        self.present(alert, animated: true, completion: nil)
    }

これで
1. タップした位置に箱を表示する
2. 「SAVE」をタップしてARWorldMapを保存する
3. 「INIT」をタップして初期化する(わかりやすくするため)
4. 「LOAD」をタップしてARWorldMapを読出して再スタート
5. 1.と同じ場所を映すと同じ位置に箱が表示される
ができる。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4