見出し画像

visionOSでARKitで検出したシーンのメッシュを可視化する

本記事でやりたいこと: visionOSで、ARKitのScene Reconstructionで検出したシーンのメッシュを可視化したい。

iOSでScene Reconstructionのメッシュを可視化した例。これをvisionOSでやりたい。

方法のひとつとしては、Xcodeの "Visualizations" 機能を使えば、ポチッとチェックを入れるだけで可視化できる。

が、そうではなくて、プログラムからシーンのメッシュを描画するにはどうするか、という話。

記事末尾に実機で動作するサンプルプロジェクトを添付しています。


公式サンプルの実装

visionOSにおけるARKitのScene Reconstructionについて、公式チュートリアルが公開されている:

以前はドキュメントだけだったが、サンプルコードも最近公開された。(※ ARKitなのでVision Pro実機でしか動作確認できない(シミュレータでは動かない))

このチュートリアルおよびサンプルコードでは、次のように MeshAnchor から ModelEntity を生成している:

for await update in sceneReconstruction.anchorUpdates {
    let meshAnchor = update.anchor

    guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue }
    switch update.event {
    case .added:
        let entity = ModelEntity()
        entity.transform = Transform(matrix: meshAnchor.originFromAnchorTransform)
        entity.collision = CollisionComponent(shapes: [shape], isStatic: true)
        entity.components.set(InputTargetComponent())
        
        entity.physicsBody = PhysicsBodyComponent(mode: .static)
        
        meshEntities[meshAnchor.id] = entity
        contentEntity.addChild(entity)
    case .updated:
        guard let entity = meshEntities[meshAnchor.id] else { continue }
        entity.transform = Transform(matrix: meshAnchor.originFromAnchorTransform)
        entity.collision?.shapes = [shape]
    case .removed:
        meshEntities[meshAnchor.id]?.removeFromParent()
        meshEntities.removeValue(forKey: meshAnchor.id)
    }
}

複雑に見えるかもしれないが、次のように CollisionComponent と InputTargetComponent と PhysicsBodyComponent を持つ(つまり、インタラクションや物理演算が可能な)ModelEntity を生成してるだけだ。

let entity = ModelEntity()
entity.transform = Transform(matrix: meshAnchor.originFromAnchorTransform)
entity.collision = CollisionComponent(shapes: [shape], isStatic: true)
entity.components.set(InputTargetComponent())

entity.physicsBody = PhysicsBodyComponent(mode: .static)

関連:

ここでのポイントは、CollisionComponent の形状(shape)として、Scene Reconstructionによって検出されたシーンのメッシュ形状を利用している点:

guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue }

...

entity.collision = CollisionComponent(shapes: [shape], isStatic: true)

衝突判定が効いているので、床やテーブルなどScene Reconstructionによって検出されたシーンの表面上に3Dオブジェクトを設置できる:

func addCube(tapLocation: SIMD3<Float>) {
    let placementLocation = tapLocation + SIMD3<Float>(0, 0.2, 0)

    let entity = ModelEntity(
        mesh: .generateBox(size: 0.1, cornerRadius: 0.0),
        materials: [SimpleMaterial(color: .systemPink, isMetallic: false)],
        collisionShape: .generateBox(size: SIMD3<Float>(repeating: 0.1)),
        mass: 1.0)

    entity.setPosition(placementLocation, relativeTo: nil)
    entity.components.set(InputTargetComponent(allowedInputTypes: .indirect))

    let material = PhysicsMaterialResource.generate(friction: 0.8, restitution: 0.0)
    entity.components.set(
        PhysicsBodyComponent(
            shapes: entity.collision!.shapes,
            mass: 1.0,
            material: material,
            mode: .dynamic)
    )

    contentEntity.addChild(entity)
}

(スクショは用意できないが、ぜひ実機で公式サンプル "SceneReconstructionExample" を動かしてみてください)

ただ、このコードではシーンのメッシュは可視化されない

InputTargetComponent や CollisionComponent が与えられているだけで、ModelEntity にメッシュ情報が与えられていないからだ。

MeshAnchor から MeshResource を生成する

SceneReconstructionProvider からは、MeshAnchor という構造体が得られる。

シーンのメッシュを可視化するには、この MeshAnchor が持っているメッシュ情報を ModelEntity に渡す必要があるわけだが、そのためには MeshResource という型にする必要がある。

@MainActor
init(
    mesh: MeshResource,
    materials: [Material] = []
)

ShapeResource であれば次のようにメソッド一発で MeshAnchor から ShapeResource インスタンスを得られるのだが、

let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor)

MeshResource には残念ながらそのような便利メソッドがない。

ではどうするかというと、

ここから先は

836字 / 1ファイル

文章やサンプルコードは多少荒削りかもしれませんが、ブログや書籍にはまだ書いていないことを日々大量に載せています。たったの400円で、すぐに購読解除してもその月は過去記事もさかのぼって読めるので、少しでも気になる内容がある方にはオトクかと思います。

技術的なメモやサンプルコード、思いついたアイデア、考えたこと、お金の話等々、頭をよぎった諸々を気軽に垂れ流しています。

最後まで読んでいただきありがとうございます!もし参考になる部分があれば、スキを押していただけると励みになります。 Twitterもフォローしていただけたら嬉しいです。 https://twitter.com/shu223/