5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ARKit Hello World (平面上に描く Hello World テキスト in 拡張現実)

Last updated at Posted at 2019-09-17

概要

  • iOS の ARKit の平面検出を使って Hello World を平面上 (水平・垂直) に表示するアプリを作る
  • Storyboard の状態を修正せず、ソースコードだけで実現する

今回の環境

  • Xcode 10.3
  • ARKit 2
  • iPhone X + iOS 12.4.1

プロジェクトの作成

Xcode にてテンプレートから iOS + Argumented Reality App を選択する。

arkit-1.png

Language に Swift を、Content Technology に SceneKit を指定する。

arkit-2.png

AppDelegate.swift と ViewController.swift という Swift のソースコードが用意される。

arkit-3.png

ソースコード

ViewController.swift を修正する。

viewDidLoad メソッドを以下のように書き換える。
SCNScene を自前で生成する。
検出した3D空間の特徴点を表示するように設定する。

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Set the view's delegate
    sceneView.delegate = self
    
    // Show statistics such as fps and timing information
    sceneView.showsStatistics = true
    
    // シーンを生成
    let scene = SCNScene()
    
    // Set the scene to the view
    sceneView.scene = scene

    // デバッグ用設定
    // 検出した3D空間の特徴点を表示する
    sceneView.debugOptions = [.showFeaturePoints]
}

viewWillAppear メソッドを以下のように書き換える。
平面検出機能を有効にする。horizontal で水平面の検出、vertical で垂直面の検出が可能になる。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    // Create a session configuration
    let configuration = ARWorldTrackingConfiguration()

    // 平面検出 (水平・垂直) が有効になるように設定
    configuration.planeDetection = [.horizontal, .vertical]
    
    // Run the view's session
    sceneView.session.run(configuration)
}

以下の renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor) メソッドを追加する。
平面を新しく検出した際に、平面可視化用の平面ジオメトリと、Hello World テキストを生成している。

// ARSCNViewDelegate#renderer
// Tells the delegate that a SceneKit node corresponding to a new AR anchor has been added to the scene.
// 新しいアンカーに対応するノードがシーンに追加されたときに呼び出される
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    
    if let planeAnchor = anchor as? ARPlaneAnchor {

        // 平面ジオメトリを作成
        let planeGeometry = ARSCNPlaneGeometry(device: sceneView.device!)!

        // 検出した平面の形状 (アンカーのジオメトリ) に平面ジオメトリを合わせる
        planeGeometry.update(from: planeAnchor.geometry)

        // 平面ジオメトリにランダムな色を設定
        let planeColor = UIColor(
            red: CGFloat.random(in: 0.0 ... 1.0),
            green: CGFloat.random(in: 0.0 ... 1.0),
            blue: CGFloat.random(in: 0.0 ... 1.0),
            alpha: 0.2) // 目立たせたくないので半透明にする
        planeGeometry.materials.first?.diffuse.contents = planeColor

        // 平面ジオメトリ用ノードを作成
        let planeNode = SCNNode(geometry: planeGeometry)

        // 表示するテキストを用意
        let text = SCNText(string: "Hello, world!", extrusionDepth: 0.0)
        text.font = UIFont.boldSystemFont(ofSize: CGFloat(planeAnchor.extent.x / 10.0))

        // テキストに色を設定: 水平面は白色、垂直面は黒色
        let textColor = planeAnchor.alignment == .horizontal ? UIColor.white : UIColor.black
        text.materials.first?.diffuse.contents = textColor
        
        // テキスト用ノードを作成
        let textNode = SCNNode(geometry: text)

        // テキストノードの中心を座標の基準にする
        let (min, max) = (textNode.boundingBox)
        let textBoundsWidth = (max.x - min.x)
        let textBoundsheight = (max.y - min.y)
        textNode.pivot = SCNMatrix4MakeTranslation(textBoundsWidth/2 + min.x, textBoundsheight/2 + min.y, 0)

        // テキストノードをx軸周りに90度回転して、検出した平面に沿わせる
        textNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1, 0, 0)

        // 検出した平面の中心にテキストノードを置く
        textNode.position = SCNVector3(planeAnchor.center)

        // 平面ジオメトリ用ノードにテキスト用ノードを追加
        planeNode.addChildNode(textNode)
        
        DispatchQueue.main.async(execute: {
            // アンカーに対応するノードに平面ジオメトリ用ノードを追加
            node.addChildNode(planeNode)
        })
    }
}

以下の renderer(SCNSceneRenderer, didUpdate: SCNNode, for: ARAnchor) メソッドを追加する。
検出した平面の形状を更新した際に、平面可視化用の平面ジオメトリと、Hello World テキストの状態を更新している。

// ARSCNViewDelegate#renderer
// Tells the delegate that a SceneKit node's properties have been updated to match the current state of its corresponding anchor.
// ノードのプロパティに現在のアンカーの状態が反映されたときに呼び出される
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    if let planeAnchor = anchor as? ARPlaneAnchor {
        DispatchQueue.main.async(execute: {
            for childNode in node.childNodes {
                // 追加したノードの平面ジオメトリを取得
                if let plainGeometry = childNode.geometry as? ARSCNPlaneGeometry {
                    // 検出した平面の形状 (アンカーのジオメトリ) に平面ジオメトリを合わせる
                    plainGeometry.update(from: planeAnchor.geometry)
                    // テキスト用ノードを更新
                    if let textNode = childNode.childNodes.first {
                        if let text = textNode.geometry as? SCNText {
                            // 検出した平面のサイズからフォントのサイズをざっくり決める
                            let size = CGFloat(min(planeAnchor.extent.x, planeAnchor.extent.z) / 10.0)
                            text.font = UIFont.boldSystemFont(ofSize: size)
                            // テキストノードの中心を座標の基準にする
                            let (min, max) = (textNode.boundingBox)
                            let textBoundsWidth = (max.x - min.x)
                            let textBoundsheight = (max.y - min.y)
                            textNode.pivot = SCNMatrix4MakeTranslation(textBoundsWidth/2 + min.x, textBoundsheight/2 + min.y, 0)
                            // 検出した平面の中心にテキストノードを置く
                            textNode.position = SCNVector3(planeAnchor.center)
                        }
                    }
                    break
                }
            }
        })
    }
}

以下の renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor) メソッドを追加する。
検出した平面の情報が削除された際に、平面可視化用の平面ジオメトリと、Hello World テキストを削除している。

// ARSCNViewDelegate#renderer
// Tells the delegate that the SceneKit node corresponding to a removed AR anchor has been removed from the scene.
// 削除されたアンカーに対応するノードが削除されたときに呼び出される
func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
    for childNode in node.childNodes {
        if childNode.geometry as? ARSCNPlaneGeometry != nil {
            // 追加した平面ジオメトリ用ノードを削除
            childNode.removeFromParentNode()
            break
        }
    }
}

実機での実行結果

実機 (iPhone X + iOS 12.4.1) にインストールして実行してみる。

Hello World テキストが地面に表示されている。

arkit-4.jpg

検出された平面の大きさに合わせて Hello World テキストが大きくなる。

arkit-5.jpg

arkit-6.jpg

壁の垂直面を検出して Hello World テキストを表示。

arkit-7.jpg

看板などの垂直の平面に Hello World テキストを表示。

arkit-8.jpg
arkit-9.jpg

階段の水平・垂直の平面に Hello World テキストを表示。

arkit-a.jpg

参考資料

5
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?