🥽

[visionOS] ARKitで検出した平面を可視化する

2023/12/19に公開

公式チュートリアルのコードは何が足りないか

visionOSにおけるARKitの平面検出については公式のチュートリアルがある。

https://developer.apple.com/documentation/visionos/placing-content-on-detected-planes

こんな感じで、PlaneAnchorclassification を「テキストで」可視化するコードが載っている:

func updatePlane(_ anchor: PlaneAnchor) {
    if entityMap[anchor.id] == nil {
        // Add a new entity to represent this plane.
        let entity = ModelEntity(
            mesh: .generateText(anchor.classification.description)
        )
        
        entityMap[anchor.id] = entity
        rootEntity.addChild(entity)
    }
    
    entityMap[anchor.id]?.transform = Transform(matrix: anchor.originFromAnchorTransform)
}

なので、同ページに載ってる動画のような、平面を色付けして可視化するようなコードにはなっていない


Apple Developer Documentationの "Placing content on detected planes" より(visionOSのスクリーンキャプチャーではありません)

単に generatePlane すれば良いのでは?

検出した平面を可視化するには、シンプルに、ModelEntity のイニシャライザに渡すメッシュ(MeshResource 型)として、generatePlane で生成した平面メッシュを渡せばよい。

let entity = ModelEntity(mesh: .generatePlane(width: anchor.geometry.extent.width, height: anchor.geometry.extent.height), materials: [material])

・・・のだが、実はこれだと実際の平面とオーバーレイした平面エンティティがずれる

Debug Visualizationメニューの "Surfaces" を有効化するとわかるのだが、
https://note.com/shu223/n/n38e7a8f345dc

  • Debug Visualizationで可視化されているSurfaces(=検出しているはずの平面)
  • ModelEntity で表示している平面メッシュ

これらがずれてしまう。(ARKitは実機でしか動作しないので現時点ではスクショを載せられないが、試してみるとわかる)

正しく PlaneAnchor を可視化するには

なぜ平面がずれるかというと、問題は PlaneAnchor.Geometry.ExtentanchorFromExtentTransform を適用していないことにある。

/// Get the transform from the plane extent to the plane anchor’s coordinate system.
public var anchorFromExtentTransform: simd_float4x4 { get }

テキストだけで可視化している場合は平面としての形状は使用していないので、エンティティのtransformに PlaneAnchor の originFromAnchorTransform を適用するだけでOKだった。

entityMap[anchor.id]?.transform = Transform(matrix: anchor.originFromAnchorTransform)

しかし、平面の形状としてPlaneAnchor.Geometry.Extent を使用するのであれば、Extentからアンカーへの変換行列である anchorFromExtentTransform も適用してやらないといけない。

そのために、次のようにアンカー自体のtransform(originFromAnchorTransform)を適用するエンティティと、平面の形状を持つ(anchorFromExtentTransform を適用する)エンティティにわける実装にした:

if let entity = entityMap[anchor.id] {
    ...
} else {
    // Add a new entity to represent this plane.
    let entity = Entity()

    let material = UnlitMaterial(color: anchor.classification.color)
    let planeEntity = ModelEntity(mesh: .generatePlane(width: anchor.geometry.extent.width, height: anchor.geometry.extent.height), materials: [material])
    planeEntity.transform = Transform(matrix: anchor.geometry.extent.anchorFromExtentTransform)

    ...

    entity.addChild(planeEntity)

    entityMap[anchor.id] = entity
    rootEntity.addChild(entity)
}

entityMap[anchor.id]?.transform = Transform(matrix: anchor.originFromAnchorTransform)

これで正しく平面が可視化されるようになった。

サンプル

サンプルコードをGitHubで公開しています。Xcode 15.1 beta 3 で動作確認済み。要実機。

https://github.com/shu223/visionOS-Sampler

(参考になったらスター、記事へのLikeいただけると嬉しいです)

Discussion