ARPlaneDetection【平面検出】
平面検出で床や壁を利用して、オブジェクトを配置できます。
1、平面検出設定でARセッションを開始
ARSCNViewから平面のトラッキング結果を受け取るために、ARSCNViewDelegateを継承します。
class ViewController: UIViewController, ARSCNViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
}
}
Plane Detection【平面検出設定】のオプションをつけてARWorldTrackingConfiguration【世界検出構成】でARKitのセッションを開始します。
ARWorldTrackingConfiguration.PlaneDetection.horizontalを設定すると地面や床やテーブル面など水平面を検出します。
.verticalを設定すると、壁などの垂直面を検出します。
.horizontalと**.vertical**を同時に検出することもできます。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = [.horizontal, .vertical]
sceneView.session.run(configuration)
}
2、検出結果を受け取る
アンカーを検出したら情報を得る
ARSCNViewのデリゲートメソッドで平面検出結果を取得します。
// 新しいアンカーがみつかったときに呼ばれるデリゲートメソッド
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
print(planeAnchor)
}
床を検出してみた結果
平面アンカーのid、向き(horizontal)、平面アンカーの中心、範囲 (1.4m,1.1m)を取得しました。
ARPlaneAnchor: 0x1165ab6c0 identifier="C843F11D-80C0-41DB-B7F5-492BA12B52FD" transform= alignment=horizontal center=(-0.011180 0.000000 -0.055902) extent=(1.498166 0.000000 1.185117)
わかりやすいようにPlane geometryを持ったSCNNodeを平面に自動追加された空のSCNNodeにaddChildしてみます。
var visualizedPlaneNode = SCNNode() // 平面視覚化用のノードをつくる
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
print(planeAnchor)
let planeGeometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) // アンカーと同じ大きさのPlaneGeometryをつくる
visualizedPlaneNode.geometry = planeGeometry // ノードのgeometryにPlaneを設定
planeGeometry.firstMaterial?.diffuse.contents = UIColor.blue.withAlphaComponent(0.7) // 青色にしてみる
visualizedPlaneNode.simdPosition = planeAnchor.center // アンカーの中心に配置
visualizedPlaneNode.eulerAngles.x = -.pi / 2 // SCNPlaneはデフォルトでは垂直なため、回転させます。
node.addChildNode(visualizedPlaneNode) // 平面に自動追加された空ノードに入れる
}
平面情報をアップデートしていく
別のレンダラーメソッドで平面情報をアップデートしていきます。
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor, // 更新された平面アンカーを取得
let visualizedPlaneNode = node.childNodes.first, // 自動追加された空のノードの子ノードを確認
let planeGeometry = visualizedPlaneNode.geometry as? SCNPlane else { return } // 子ノードのplanegeometryを確認
planeGeometry.width = CGFloat(planeAnchor.extent.x) // 幅を更新された平面アンカーの幅に
planeGeometry.height = CGFloat(planeAnchor.extent.z) // 高さを更新された平面アンカーの高さに
visualizedPlaneNode.simdPosition = planeAnchor.center // 中心を更新された平面アンカーの中心に
}
ちなみに、組み込まれている検出された平面アンカーそのものの正方形ではないgeometryの形を取ることもできます。
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor,
let meshGeometry = ARSCNPlaneGeometry(device: sceneView.device!)
else { fatalError("Can't create plane geometry") }
meshGeometry.update(from: planeAnchor.geometry)
meshNode = SCNNode(geometry: meshGeometry)
meshNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red.withAlphaComponent(0.7)
node.addChildNode(meshNode)
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor
if let meshGeometry = meshNode.geometry as? ARSCNPlaneGeometry {
meshGeometry.update(from: planeAnchor.geometry)
}
}
3、検出した平面の利用
壁紙を貼ってみる
planeGeometry.firstMaterial?.diffuse.contents = UIImage(named:"flowerWallPaper")
オブジェクトをおいてみる
画面をタップした先に平面geometry(ここではARPlaneGeometryをとってます)があれば、オブジェクトのy座標を平面ノードに合わせて、x,z軸を平面とタップの交差点に合わせます。
@objc func sceneViewTapped(recognizer:UITapGestureRecognizer) {
let location = recognizer.location(in: sceneView)
let hitResults = sceneView.hitTest(location, options: [:])
if !hitResults.isEmpty {
guard let planeNode = hitResults.first?.node else {return}
let interiorHeight = interior.boundingBox.max.y-interior.boundingBox.min.y // オブジェクトノードの高さを計算
let tappedCoodinates = hitResults.first?.localCoordinates // タップと平面ノードの交差点
interior.position = SCNVector3(tappedCoodinates!.x, interiorHeight/2, tappedCoodinates!.z) // オブジェクトの原点はオブジェクトの中心にしたので、高さの半分だけ上に置く
planeNode.addChildNode(interior)
}
壁でオブジェクトを遮蔽する
.verticalの子ノードを手前にレンダリングされるようにすることで、後ろのノードが壁に隠れている表現ができます。
if planeAnchor.alignment == .vertical {
planeNode.renderingOrder = -1
planeNode.geometry!.firstMaterial!.colorBufferWriteMask = []
}
🐣
フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。