スポンサーリンク

【Swift】ARkitで平面に文字を出力する方法ご紹介。

Swift
スポンサーリンク

今回の記事は、XcodeのARkitを使ってAR画面の平面を取得するともに、その平面に文字を記載する方法を記載していきます。
AR技術に関してはかなり初心者なので初心者の方も分かりやすいように記事を記載記載していきます。

画像汚いですが下記のようなイメージになります。

では早速メイン記事に進みます。

スポンサーリンク

AR画面に文字を出力するまでのXcode設定

ARを使用する際の設定は非常に簡単です利用として、ライブラリの整理がかなり進んでいるためです。行うことは下記のようにプロジェクトを作成していきます。

こちらがARを含むプロジェクトのテンプレートのようなものなのでこちらでお手軽に進めていきましょう。

content technologyはScenekitを選択します。

ここは初めの状態では、Realitykitみたいなものになっているので変更が必要です。

Xcode側の設定はここで終了です。

AR画面に文字を出力する際のコード解説

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set the view's delegate
        sceneView.delegate = self
        
        // Show statistics such as fps and timing information
        sceneView.showsStatistics = true
        
        // Create a new scene
        let scene = SCNScene()
        
        // Set the scene to the view
        sceneView.scene = scene
        
        sceneView.debugOptions = [.showFeaturePoints]
    }
    
    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)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Pause the view's session
        sceneView.session.pause()
    }

    
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

        if let planeAnchor = anchor as? ARPlaneAnchor {

            let planeGeometry = ARSCNPlaneGeometry(device: sceneView.device!)!
            print(planeGeometry)
            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.9)
            planeGeometry.materials.first?.diffuse.contents = planeColor

            let planeNode = SCNNode(geometry: planeGeometry)
            print(planeNode)
            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)
            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)
            })
        }
    }
    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
                    }
                }
            })
        }
    }
    func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
        for childNode in node.childNodes {
            if childNode.geometry as? ARSCNPlaneGeometry != nil {
                
                childNode.removeFromParentNode()
                break
            }
        }
    }

    func session(_ session: ARSession, didFailWithError error: Error) {
        // Present an error message to the user   
    }
    func sessionWasInterrupted(_ session: ARSession) {
        // Inform the user that the session has been interrupted, for example, by presenting an overlay   
    }
    func sessionInterruptionEnded(_ session: ARSession) {
        // Reset tracking and/or remove existing anchors if consistent tracking is required       
    }
}

まずは上記が全てのコードです。

簡単に解説していきます。「renderer」関数がAR空間に何か操作を行う場合に処理を行う関数になります。それをdelegetaで回して処理を行なっていきます。「renderer」関数を見ていきましょうか。

    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.9) // 目立たせたくないので半透明にする
            planeGeometry.materials.first?.diffuse.contents = planeColor

            // 平面ジオメトリ用ノードを作成
            let planeNode = SCNNode(geometry: planeGeometry)
            // 表示するテキストを用意(今回はHello Worldを表示)
            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)
            })
        }
    }
    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
                    }
                }
            })
        }
    }
    func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
        for childNode in node.childNodes {
            if childNode.geometry as? ARSCNPlaneGeometry != nil {
                // 追加した平面ジオメトリ用ノードを削除
                childNode.removeFromParentNode()
                break
            }
        }
    }

これ自体は、ネットを参考にさせていただいたものなのでよくあるコードなのでそんなに貴重なコードではないです。コード中に簡単な説明を追記しています。

平面の取得→文字をノードに追加→そのノードを平面のノードに追加の流れに沿って表示を行っていきます。

今回の記事は以上です。他にもAR関連の記事を記載しているので気になる方は他の記事に関してもご参照ください。

おすすめ情報ご紹介。

下記リンクよりU-NEXTの契約をすると31日間無料でU-NEXTの全動画約21万作品を見放題です。31日期間に解約すれば0円です。今だけのおすすめ情報なのでまだ登録していない人は是非!

スポンサーリンク
Swift
スポンサーリンク
シェアする
スポンサーリンク
SunnyDayTravel-Blog

コメント

タイトルとURLをコピーしました