ARKitで「非」矩形な平面を検出する


iOS 11.3(いわゆるARKit 1.5)より、任意の2次元形状のジオメトリで平面を検出できるようになりました。たとえば次のgifのように、丸テーブルの平面を円形(に近い)ジオメトリとして検出できます。

なお、これから解説するAPIを見るとわかりますが、水平面だけでなく、ARKit 1.5で検出可能になった垂直平面にも本機能は有効です。

ARSCNPlaneGeometry

本機能において鍵となるのはARSCNPlaneGeometryというiOS 11.3(ARKit 1.5)の新クラスです。

SCNGeometryを継承しており、ARKitが検出した平面の2次元形状を表すためのクラスです。

このクラスのイニシャライザはひとつしかなく、

init?(device: MTLDevice)

このように、MTLDeviceオブジェクトを渡すようになっています。つまり、Metalサポート必須ということです。

ARSCNPlaneGeometry is available only in SceneKit views or renderers that use Metal. This class is not supported for OpenGL-based SceneKit rendering.

個人的には、こういうAPIで明示的にMetalのクラス(MTLDeviceはプロトコルですが)が必要になるというのも珍しいなと思いました。たとえばCore MLは内部的にMetalを用いていても一切Core MLフレームワークのAPIには出てきませんし、SceneKitやCore Imageも明示的にMetalを利用する部分以外では出てきません。

ARPlaneAnchorのgeometryプロパティ

iOS 11.3でARPlaneAnchorgeometryというプロパティが追加されました。型はARPlaneGeometryです。

ARPlaneGeometryはARKitが検出した平面の形状のメッシュ情報を保持するクラスです。プロパティから、頂点座標や頂点インデックスにアクセスできます。

@property(nonatomic, readonly) const vector_float3 *vertices;
@property(nonatomic, readonly) const int16_t *triangleIndices;

なお、こちらの親クラスはNSObjectであり、SCNGeometryではありません。つまりこれはSceneKitのノードにジオメトリとしてそのまま割り当てられるものではないということです。ARAnchorSCNNodeではない、というのと同様に考えればいいでしょう。

このオブジェクトをARSCNPlaneGeometryupdate(from:)メソッドに渡すことでジオメトリを更新できます。

func update(from: ARPlaneGeometry)

実装方法

ARSCNPlaneGeometryを初期化する

MTLDeviceオブジェクトを作成し、ARSCNPlaneGeometryを初期化します。

let device = MTLCreateSystemDefaultDevice()!
planeGeometry = ARSCNPlaneGeometry(device: device)!

MTLDeviceやそのデフォルトデバイスについては「Metal入門」で解説しています。

平面検出時/更新時にARSCNPlaneGeometryを更新する

平面検出時/更新時に、ARPlaneAnchorオブジェクトのgeometryプロパティから取得したARPlaneGeometryオブジェクトを渡してARSCNPlaneGeometryオブジェクトを更新します。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()}
    planeGeometry.update(from: planeAnchor.geometry)
    ...
}

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else {fatalError()}
    planeGeometry.update(from: planeAnchor.geometry)
}

ARKit-Samplerにサンプルコード入ってます&すぐにビルドして試せます。

以上。