ARKit PlaneDetectionProvider: occlusion material

We can use Occlusion material to hide planes while letting them participate in collisions and physics.

Overview

Let’s build on the example from creating simple planes from anchors. We’ll add collision and physics components, then replace the material with OcclusionMaterial. This will render the passthrough feed in place of the debug materials we were using. This is a great way to include entities for collisions and physics without rendering them in the scene.

func createSimplePlaneEntity(for anchor: PlaneAnchor) -> Entity {

    let extent = anchor.geometry.extent
    let mesh = MeshResource.generatePlane(width: extent.width, height: extent.height)
    let material = OcclusionMaterial()

    let entity = ModelEntity(mesh: mesh, materials: [material])
    entity.transform = Transform(matrix: matrix_multiply(anchor.originFromAnchorTransform, extent.anchorFromExtentTransform))

    entity.generateCollisionShapes(recursive: true, static: true)
    let physicsMaterial = PhysicsMaterialResource.generate(friction: 0, restitution: 1)
    let physics = PhysicsBodyComponent(massProperties: .default, material: physicsMaterial, mode: .static)
    entity.components.set(physics)

    return entity
    
}

We’ll let RealityKit generate a simple collision shape based on the entity using generateCollisionShapes. To generate more detailed shapes see adding collisions and physics.

Video Demo

Full Example Code

struct Example073: View {
    @State var session = ARKitSession()

    @State var planeAnchorsSimple: [UUID: Entity] = [:]

    // An entity with physics that can bounce around the room
    @State var subject : ModelEntity = {
        let subject = ModelEntity(
            mesh: .generateSphere(radius: 0.1),
            materials: [SimpleMaterial(color: .stepRed, isMetallic: false)])
        subject.setPosition([1, 1, -1], relativeTo: nil)

        let collision = CollisionComponent(shapes: [.generateSphere(radius: 0.1)])

        var physics = PhysicsBodyComponent(
            shapes: [.generateSphere(radius: 0.1)],
            mass: 1.0,
            material: .generate(friction: 0, restitution: 1),
            mode: .dynamic
        )
        physics.isAffectedByGravity = false

        let input = InputTargetComponent()
        subject.components.set([collision, physics, input])

        return subject
    }()


    var body: some View {
        RealityView { content in
            content.add(subject)
        } update: { content in

            for (_, entity) in planeAnchorsSimple {
                if !content.entities.contains(entity) {
                    content.add(entity)
                }
            }
        }
        .gesture(TapGesture()
            .targetedToEntity(subject)
            .onEnded { value in
                // Add some force when we tap the subject
                let force = SIMD3<Float>(
                    x: Float.random(in: -1...1),
                    y: Float.random(in: -1...1),
                    z: Float.random(in: -1...1)
                )
                var motion = PhysicsMotionComponent()
                motion.linearVelocity = force * 3
                value.entity.components.set(motion)
            })
        .task {
            try! await setupAndRunPlaneDetection()
        }
    }

    func setupAndRunPlaneDetection() async throws {
        let planeData = PlaneDetectionProvider(alignments: [.horizontal, .vertical, ])
        if PlaneDetectionProvider.isSupported {
            do {
                try await session.run([planeData])
                for await update in planeData.anchorUpdates {
                    switch update.event {
                    case .added, .updated:
                        let anchor = update.anchor

                        let planeEntitySimple = createSimplePlaneEntity(for: anchor)
                        planeAnchorsSimple[anchor.id] = planeEntitySimple

                    case .removed:
                        let anchor = update.anchor
                        planeAnchorsSimple.removeValue(forKey: anchor.id)

                    }
                }
            } catch {
                print("ARKit session error \(error)")
            }
        }
    }

    func createSimplePlaneEntity(for anchor: PlaneAnchor) -> Entity {

        let extent = anchor.geometry.extent
        let mesh = MeshResource.generatePlane(width: extent.width, height: extent.height)
        let material = OcclusionMaterial()

        let entity = ModelEntity(mesh: mesh, materials: [material])
        entity.transform = Transform(matrix: matrix_multiply(anchor.originFromAnchorTransform, extent.anchorFromExtentTransform))

        // We'll let RealityKit generate a simple collision shape based on the entity.
        // For more detailed shapes see: https://stepinto.vision/example-code/arkit-planedetectionprovider-adding-collisions-and-physics/
        entity.generateCollisionShapes(recursive: true, static: true)
        let physicsMaterial = PhysicsMaterialResource.generate(friction: 0, restitution: 1)
        let physics = PhysicsBodyComponent(massProperties: .default, material: physicsMaterial, mode: .static)
        entity.components.set(physics)

        return entity
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?