Can not remove final World Anchor

I’ve been having some issues removing anchors. I can add anchors with no issue. They will be there the next time I run the scene. I can also get updates when ARKit sends them. I can remove anchors, but not all the time. The method I’m using is to call removeAnchor() on the data provider.

worldTracking.removeAnchor(forID: uuid)
// Yes, I have also tried `removeAnchor(_ worldAnchor: WorldAnchor)`

This works if there are more than one anchor in a scene. When I’m down to one remaining anchor, I can remove it. It seems to succeed (does not raise an error) but the next time I run the scene the removed anchor is back. This only happens when there is only one remaining anchor.

do {
  // This always run, but it doesn't seem to "save" the removal when there is only one anchor left.
  try await worldTracking.removeAnchor(forID: uuid)
} catch {
  // I have never seen this block fire!
  print("Failed to remove world anchor \(uuid) with error: \(error).")
}

I posted a video on my website if you want to see it happening. https://stepinto.vision/labs/lab-051-issues-with-world-tracking/

Here is the full code. Can you see if I’m doing something wrong? Is this a bug?

struct Lab051: View {
    @State var session = ARKitSession()
    @State var worldTracking = WorldTrackingProvider()

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

    @State var placement = Entity()

    @State var subject : ModelEntity = {
        let subject = ModelEntity(
            mesh: .generateSphere(radius: 0.06),
            materials: [SimpleMaterial(color: .stepRed, isMetallic: false)])
        subject.setPosition([0, 0, 0], relativeTo: nil)

        let collision = CollisionComponent(shapes: [.generateSphere(radius: 0.06)])
        let input = InputTargetComponent()
        subject.components.set([collision, input])

        return subject
    }()

    var body: some View {
        RealityView { content in

            guard let scene = try? await Entity(named: "WorldTracking", in: realityKitContentBundle) else { return }
            content.add(scene)

            if let placementEntity = scene.findEntity(named: "PlacementPreview") {
                placement = placementEntity
            }

        } update: { content in
            for (_, entity) in worldAnchorEntities {
                if !content.entities.contains(entity) {
                    content.add(entity)
                }
            }
        }
        .modifier(DragGestureImproved())
        .gesture(tapGesture)
        .task {
            try! await setupAndRunWorldTracking()
        }
    }

    var tapGesture: some Gesture {
        TapGesture()
            .targetedToAnyEntity()
            .onEnded { value in

                if value.entity.name == "PlacementPreview" {
                    // If we tapped the placement preview cube, create an anchor
                    Task {
                        let anchor = WorldAnchor(originFromAnchorTransform: value.entity.transformMatrix(relativeTo: nil))
                        try await worldTracking.addAnchor(anchor)
                    }
                } else {
                    Task {
                        // Get the UUID we stored on the entity
                        let uuid = UUID(uuidString: value.entity.name) ?? UUID()

                        do {
                            try await worldTracking.removeAnchor(forID: uuid)
                        } catch {
                            print("Failed to remove world anchor \(uuid) with error: \(error).")
                        }

                    }
                }
            }
    }

    func setupAndRunWorldTracking() async throws {

        if WorldTrackingProvider.isSupported {
            do {
                try await session.run([worldTracking])

                for await update in worldTracking.anchorUpdates {
                    switch update.event {
                    case .added:

                        let subjectClone = subject.clone(recursive: true)
                        subjectClone.isEnabled = true
                        subjectClone.name = update.anchor.id.uuidString
                        subjectClone.transform = Transform(matrix: update.anchor.originFromAnchorTransform)

                        worldAnchorEntities[update.anchor.id] = subjectClone
                        print("🟢 Anchor added \(update.anchor.id)")

                    case .updated:

                        guard let entity = worldAnchorEntities[update.anchor.id] else {
                            print("No entity found to update for anchor \(update.anchor.id)")
                            return
                        }

                        entity.transform = Transform(matrix: update.anchor.originFromAnchorTransform)

                        print("🔵 Anchor updated \(update.anchor.id)")

                    case .removed:

                        worldAnchorEntities[update.anchor.id]?.removeFromParent()
                        worldAnchorEntities.removeValue(forKey: update.anchor.id)
                        print("🔴 Anchor removed \(update.anchor.id)")

                        if let remainingAnchors = await worldTracking.allAnchors {
                            print("Remaining Anchors: \(remainingAnchors.count)")
                        }
                    }
                }
            } catch {
                print("ARKit session error \(error)")
            }
        }
    }
}

I can confirm this same issue is also occurring on my device.

I am using visionOS Developer Beta 2.5, and can reproduce this issue by using Apple's Sample Code Building local experiences with room tracking.

If Apple could confirm that this is either;

  • A bug in the ARKit World Anchor persistence
  • or expected behaviour, that ARKit World Anchors must persist at least one anchor

that would be very helpful for us!

Can not remove final World Anchor
 
 
Q