はじめに
こちらの続きです▶︎ ARKitのはじめかた その1「5分で出来るARアプリ」
こんにちは!
ARKitのまとめ記事 にて書いた実装方法について
先ずはARKit1時代に使われていた「オブジェクト配置の方法」を書きます。
とはいえ、今もよく使う方法なのでモジュール的な感じで参考にして頂けると幸いです。
ゴール
前提
ARKitのはじめかた その1「5分で出来るARアプリ」で作成した環境をベースとします。
※依存関係は無いのでオブジェクトを置き換えれば他でも使えると思います。
コード
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.scene = SCNScene()
let gesture = UITapGestureRecognizer(target: self, action:#selector(onTap))
self.sceneView.addGestureRecognizer(gesture)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
@objc func onTap(sender: UITapGestureRecognizer) {
guard let scene = SCNScene(named: "ship.scn",inDirectory: "art.scnassets") else { return }
let shipNode = (scene.rootNode.childNode(withName: "ship", recursively: false))!
let pos = sender.location(in: sceneView)
let results = sceneView.hitTest(pos, types: .featurePoint)
if !results.isEmpty {
let hitTestResult = results.first!
let transform = hitTestResult.worldTransform
shipNode.position = SCNVector3 (
transform.columns.3.x,
transform.columns.3.y,
transform.columns.3.z
)
sceneView.scene.rootNode.addChildNode(shipNode)
}
}
}
解説
1.空のシーンを読み込んで、iPhoneの画面に反映させます。
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.scene = SCNScene()
//画面に、何もオブジェクトが無い空のシーン(映像空間)を適用させます。
let gesture = UITapGestureRecognizer(target: self, action:#selector(onTap))
self.sceneView.addGestureRecognizer(gesture)
//タップジェスチャーを追加
}
2.現実とカメラ越しの映像を連携させます。(AR機能をONにする)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
//AR環境の設定ファイル作ります。これによりAR機能が使えるようになります。
sceneView.session.run(configuration)
//画面の設定にARの設定を反映させます。
}
3.タッチした場所にオブジェクト配置します。
@objc func onTap(sender: UITapGestureRecognizer) {
//画面にタッチした時に発動します。タッチした場所を、senderという変数に入れます。
guard let scene = SCNScene(named: "ship.scn",inDirectory: "art.scnassets") else { return }
//art.scnassetsディレクトリにあるship.scnという飛行機モデルを、画面の映像を作るシーンという変数に入れます。
let shipNode = (scene.rootNode.childNode(withName: "ship", recursively: false))!
//sceneの中にあるshipという飛行機のモデルだけを、shipNodeに入れます。(この後sceneView.sceneで使えるように取り出しました)
let pos = sender.location(in: sceneView)
//一番始めにタッチしたsceneView上の場所をposとします。
let results = sceneView.hitTest(pos, types: .featurePoint)
//posの延長線上にある特徴点を手前から順番にresultsに入れます。
if !results.isEmpty {
let hitTestResult = results.first!
//resultsがあれば、一番初め(手前)の特徴点をhitTestResultとします。
let transform = hitTestResult.worldTransform
//hitTestResultを空間全体での座標(transform)に変換します。
shipNode.position = SCNVector3 (
transform.columns.3.x,
transform.columns.3.y,
transform.columns.3.z
//特徴点座標(transform)の横,高さ,奥行きをshipNodeの場所として割り当てます。
)
sceneView.scene.rootNode.addChildNode(shipNode)
//場所が決まったshipNodeを配置します。
}
}
■hitTestとは?
ARSCNView上で、タッチした箇所を起点として手前から延長線上に特徴点が無いか調べます。
特徴点があれば、見つけた順に特徴点の箇所を記録します。
ARSCNViewで行うhitTestはARHitTestResultで、取得出来る特徴点のタイプは予め指定できます。
参考:[ARKit] HitTestで使うタイプをうまく使い分ける
#まとめ
ARKit1〜1.5時代はこの方法がメインでした。
しかし、この座標は端末位置との絶対座標の為、他者との共有は難しいです。
また、取得した場所と同じ場所をオブジェクトに割当てているだけなので、環境の変化には適合出来ません。
例:ある絵の上にオブジェクトを置いた場合、絵が動くとオブジェクトはそのままの位置に残る。(オブジェクトと絵の関係性は無い)
次回は、このあたりを補う事が出来る ARKit2以降で使うようになったオブジェクト配置の方法を書こうと思います。
ここまで読んで頂きありがとうございました!