visionOSでARKitで検出したシーンのメッシュを可視化する
本記事でやりたいこと: visionOSで、ARKitのScene Reconstructionで検出したシーンのメッシュを可視化したい。
方法のひとつとしては、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 には残念ながらそのような便利メソッドがない。
ではどうするかというと、
最後まで読んでいただきありがとうございます!もし参考になる部分があれば、スキを押していただけると励みになります。 Twitterもフォローしていただけたら嬉しいです。 https://twitter.com/shu223/