Xcode
iOS
アプリ
Swift
ARKit

【ARKit入門】平面にアイテムを配置する

平面にオブジェクトを配置する

こんな感じのです
IMG_4898.TRIM.MOV.gif

この記事ではARKitを用いて、検出された平面にアイテムを配置する実装を紹介します。

ARKitでの初期設定や、アイテムとして追加する3Dモデルのプロジェクトへの取り込みなどは以下をご参照ください〜。;)
【ARKit入門】 まずやること
【ARKit入門】Xcodeに3Dファイルを入れてみる

ではでは本題です!!

タップの検出

ARSCNViewがタップされた時にその情報を受け取る必要があるので、まずその検出をします。tappedメソッドについては後述します。

MainViewController.swift
/// メインのビューのタップを検知するように設定する
    func registerGestureRecognizers() {

        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
        self.mainSceneView.addGestureRecognizer(tapGestureRecognizer)
    }

アイテムボタン

ボタンがタップされたら、対応するアイテムを選択状況とします。
selectedItemの値は.scnファイルのファイル名とします。

MainViewController.swift
@IBAction func cupButtonTapped(_ sender: Any) {
        selectedItem = "cup"
    }

    @IBAction func wineButtonTapped(_ sender: Any) {
        selectedItem = "wine_glass"
    }

画面タップ時の処理

画面がタップされたらsenderよりタップ位置を取得し、ARアンカーを検索します。そこでARアンカーの情報が所得されていればアイテム追加処理をします。

MainViewController.swift
@objc func tapped(sender: UITapGestureRecognizer) {
        // タップされた位置を取得する
        let sceneView = sender.view as! ARSCNView
        let tapLocation = sender.location(in: sceneView)

     // タップされた位置のARアンカーを探す
        let hitTest = sceneView.hitTest(tapLocation, types: .existingPlaneUsingExtent)
        if !hitTest.isEmpty {
            // タップした箇所が取得できていればitemを追加
            self.addItem(hitTestResult: hitTest.first!)
        }
    }

アイテム追加処理

選択したアイテムに対応する.scnファイルを使用してノードを作成し、現実世界の座標を検出しそこにアイテムを配置します。

MainViewController.swift
    /// アイテム配置メソッド
    func addItem(hitTestResult: ARHitTestResult) {
        if let selectedItem = self.selectedItem {

            // アセットのより、シーンを作成
            let scene = SCNScene(named: "Models.scnassets/\(selectedItem).scn")

            // ノード作成
            let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))!

            // 現実世界の座標を取得
            let transform = hitTestResult.worldTransform
            let thirdColumn = transform.columns.3

            // アイテムの配置
            node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)
            self.mainSceneView.scene.rootNode.addChildNode(node)
        }
    }

以上で、実装完了です!!
最後に全体のサンプルコードを載せていますのでご参考ください。

また、今後もARKit入門記事を投稿予定です!ARKitを用いたサンプルアプリ、ARKitで使用されているメソッド、プロパティの詳細についてまとめていこうと思っています。お楽しみにっ!

サンプルコード全体

MainViewController.swift
import UIKit
import ARKit

class ViewController: UIViewController{

    @IBOutlet weak var mainSceneView: ARSCNView!

    let configuration = ARWorldTrackingConfiguration()
    var selectedItem: String?

    override func viewDidLoad() {
        super.viewDidLoad()

        initialize()
        registerGestureRecognizers()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    /// ARSCNiew初期化設定    
    func initialize (){

        self.mainSceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin, ARSCNDebugOptions.showFeaturePoints]
        self.configuration.planeDetection = .horizontal
        self.mainSceneView.session.run(configuration)
        self.mainSceneView.autoenablesDefaultLighting = true
    }

    /// メインのビューのタップを検知するように設定する
    func registerGestureRecognizers() {

        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
        self.mainSceneView.addGestureRecognizer(tapGestureRecognizer)
    }

    @objc func tapped(sender: UITapGestureRecognizer) {
        // タップされた位置を取得する
        let sceneView = sender.view as! ARSCNView
        let tapLocation = sender.location(in: sceneView)

        // タップされた位置のARアンカーを探す
        let hitTest = sceneView.hitTest(tapLocation, types: .existingPlaneUsingExtent)
        if !hitTest.isEmpty {
            // タップした箇所が取得できていればitemを追加
            self.addItem(hitTestResult: hitTest.first!)
        }
    }

    /// アイテム配置メソッド
    func addItem(hitTestResult: ARHitTestResult) {
        if let selectedItem = self.selectedItem {

            // アセットのより、シーンを作成
            let scene = SCNScene(named: "Models.scnassets/\(selectedItem).scn")

            // ノード作成
            let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))!

            // 現実世界の座標を取得
            let transform = hitTestResult.worldTransform
            let thirdColumn = transform.columns.3

            // アイテムの配置
            node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)
            self.mainSceneView.scene.rootNode.addChildNode(node)
        }
    }


    @IBAction func cupButtonTapped(_ sender: Any) {
        selectedItem = "cup"
    }

    @IBAction func wineButtonTapped(_ sender: Any) {
        selectedItem = "wine_glass"
    }
}