しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

ARKitでタップした場所に立方体を配置する

iOS11で登場したARKitを使って、画面上に立方体を配置してみました。
下のように地面に複数のオブジェクトが並ぶようにしています。

f:id:llcc:20171006144302p:plain

まずはプロジェクト作成から「Augmented Reality App」を選んでARKitのテンプレートを作成します。

f:id:llcc:20171006144342p:plain

次はサンプルコードを消してコードを最小限にします。

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {
    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        sceneView.delegate = self
        sceneView.showsStatistics = true
        let scene = SCNScene()
        sceneView.scene = scene
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        sceneView.session.pause()
    }
}

次はデバック用オプションを指定します。
このオプションは認識した特徴点を表示してくれるものです。
特徴点が一定数を超えるとその場所を平面としてみなしてくれます。

class ViewController: UIViewController, ARSCNViewDelegate {
    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        // 省略
        
        sceneView.debugOptions = ARSCNDebugOptions.showFeaturePoints
    }

    // 省略
}

実行すると画面に特徴点が黄色い点として現れます。

f:id:llcc:20171006144658p:plain

次は平面認識をする設定をします。
ARWorldTrackingConfigurationのplaneDetectionに.horizontalをセットします。
今は.horizontalしかないので、平面以外の認識はできません。

class ViewController: UIViewController, ARSCNViewDelegate {
    // 省略
        
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal // 今回追加
        sceneView.session.run(configuration)
    }
}

最後にタップ時にボックスを配置する処理を記述します。
下のようにUITapGestureRecognizerのセットと、タップ時に呼ばれるメソッドを実装します。

class ViewController: UIViewController, ARSCNViewDelegate {
    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        // 省略
        
        let gesture = UITapGestureRecognizer(target: self, action: #selector(tapView))
        sceneView.addGestureRecognizer(gesture)
    }

    @objc func tapView(sender: UITapGestureRecognizer) {
        let location = sender.location(in: sceneView)
        
        let hitTestResult = sceneView.hitTest(location, types: .existingPlane)
        if let result = hitTestResult.first {
            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 node = SCNNode(geometry: geometry)
            node.position = SCNVector3(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y + 0.20, result.worldTransform.columns.3.z)
            sceneView.scene.rootNode.addChildNode(node)
        }
    }
    
    // 省略
}

場所の取得は下メソッドで行っています。
locationメソッドで画面上のタップ位置を取得して、hitTestでタップ位置と認識済平面との交点の座標を計算しています。

取得後は、普通のSceneKitアプリ同様にボックスを配置すれば完成です。

let location = sender.location(in: sceneView)
let hitTestResult = sceneView.hitTest(location, types: .existingPlane)

f:id:llcc:20171006144302p:plain

参考URL

[iOS 11][ARKit] 物理衝突を実装して、キューブを落として見る #WWDC2017 | Developers.IO