Update World Anchor using object anchor

Hi. I display buildings in mixed immersive view. Right now the building appears in relation to the person when the view is opened. (world anchor)

To position the building precisely, I want to use object tracking. Set up a project following the wwdc object tracking session. That works well sort of...

With an object anchor, the 3D object related to the anchor disappears as soon as the Tracked object is out of view, and with the big objects you don't get the chance to look around.

I figure I need to give my 3D object a world anchor, and only have that world anchor update if a change in the object anchor is detected. how do I do that?

Preferable using the tools in Reality Composer pro (or very well explained, as I am new to code)

Answered by Vision Pro Engineer in 800133022

Hi @Ilskov

Your proposed solution will work. Here's how to implement it.

In Reality Composer Pro, create a new component named ObjectToWorldComponent and add it to the entity with the object anchor component. Don't forget to save the file.

In Xcode, open Packages/RealityKitContent/Sources/RealityKitContent/ObjectToWorldComponent.swift and remove the name property; we won't be using it.

Create a system (for more information on Entity Component System see Understanding RealityKit’s modular architecture) to transfer the contents and location of your object anchor to a world anchor.

struct ObjectToWorldSystem: System {
    // Create a private component to hold component properties
    // we don't want to expose in Reality Composer Pro.
    struct PrivateObjectToWorldComponent: Component {
        let worldAnchorEntity:AnchorEntity
    }
    
    let query = EntityQuery(where: .has(ObjectToWorldComponent.self))
    
    public init(scene: RealityKit.Scene) { }
    
    public func update(context: SceneUpdateContext) {
        let entities = context.entities(matching: self.query, updatingSystemWhen: .rendering)
        
        for entity in entities {
            guard let objectAnchorEntity = entity as? AnchorEntity,
                  objectAnchorEntity.isAnchored else {continue}
            let objectTransform = entity.transformMatrix(relativeTo: nil)

            if let privateComponent = entity.components[PrivateObjectToWorldComponent.self] {
                // Update the position of the world anchor when the object moves.
                
                let modelPosition = entity.position(relativeTo: nil)
                let worldAnchorPosition = privateComponent.worldAnchorEntity.position(relativeTo: nil)
                let distance = distance(modelPosition, worldAnchorPosition)
                
                // Set to 1cm
                let moveThreshold:Float = 0.01
                
                // The object moved more than 1cm. Update the world anchor.
                if distance > moveThreshold {
                    let newWorldAnchorEntity = AnchorEntity(.world(transform: objectTransform))
                    let existingWorldAnchorEntity = privateComponent.worldAnchorEntity
                    
                    existingWorldAnchorEntity.children.forEach { child in
                        newWorldAnchorEntity.addChild(child)
                    }
                    
                    existingWorldAnchorEntity.parent?.addChild(newWorldAnchorEntity)
                    existingWorldAnchorEntity.removeFromParent()
                    entity.components.set(PrivateObjectToWorldComponent(worldAnchorEntity: newWorldAnchorEntity))
                }
            }
            else {
                // Move the contents from an object anchor to a world anchor
                let worldAnchorEntity = AnchorEntity(.world(transform: objectTransform))
                let privateComponent = PrivateObjectToWorldComponent(worldAnchorEntity: worldAnchorEntity)

                entity.children.forEach { child in
                    worldAnchorEntity.addChild(child)
                }
                
                entity.parent?.addChild(worldAnchorEntity)
                entity.components.set(privateComponent)
            }
        }
    }
}

Register the system and component in your app's initializer. Put this code in the file generated by Xcode that ends in "App".

init() {
    ObjectToWorldComponent.registerComponent()
    ObjectToWorldSystem.registerSystem()
}

Add code to your view to start a SpatialTrackingSession to obtain a person's permission to query the location of world and object anchor entities.

Important: to help protect people’s privacy, visionOS limits app access to cameras and other sensors in Apple Vision Pro. You need to add an NSWorldSensingUsageDescription to your app’s information property list to provide a usage description that explains how your app uses the data these sensors provide.

struct ImmersiveView: View {
    let spatialTrackingSession = SpatialTrackingSession()
    
    var body: some View {
        RealityView { content in
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)
            }
        }
        .task {
            // Start a spatial tracking session to obtain permission to query the object and world anchor transforms.
            let configuration = SpatialTrackingSession.Configuration(
                tracking: [.object, .world])
            await spatialTrackingSession.run(configuration)
        }
    }
}

Note: In Reality Composer Pro you can add ObjectToWorldComponent to any entity that has an object anchor component and it will adopt the desired behavior.

Accepted Answer

Hi @Ilskov

Your proposed solution will work. Here's how to implement it.

In Reality Composer Pro, create a new component named ObjectToWorldComponent and add it to the entity with the object anchor component. Don't forget to save the file.

In Xcode, open Packages/RealityKitContent/Sources/RealityKitContent/ObjectToWorldComponent.swift and remove the name property; we won't be using it.

Create a system (for more information on Entity Component System see Understanding RealityKit’s modular architecture) to transfer the contents and location of your object anchor to a world anchor.

struct ObjectToWorldSystem: System {
    // Create a private component to hold component properties
    // we don't want to expose in Reality Composer Pro.
    struct PrivateObjectToWorldComponent: Component {
        let worldAnchorEntity:AnchorEntity
    }
    
    let query = EntityQuery(where: .has(ObjectToWorldComponent.self))
    
    public init(scene: RealityKit.Scene) { }
    
    public func update(context: SceneUpdateContext) {
        let entities = context.entities(matching: self.query, updatingSystemWhen: .rendering)
        
        for entity in entities {
            guard let objectAnchorEntity = entity as? AnchorEntity,
                  objectAnchorEntity.isAnchored else {continue}
            let objectTransform = entity.transformMatrix(relativeTo: nil)

            if let privateComponent = entity.components[PrivateObjectToWorldComponent.self] {
                // Update the position of the world anchor when the object moves.
                
                let modelPosition = entity.position(relativeTo: nil)
                let worldAnchorPosition = privateComponent.worldAnchorEntity.position(relativeTo: nil)
                let distance = distance(modelPosition, worldAnchorPosition)
                
                // Set to 1cm
                let moveThreshold:Float = 0.01
                
                // The object moved more than 1cm. Update the world anchor.
                if distance > moveThreshold {
                    let newWorldAnchorEntity = AnchorEntity(.world(transform: objectTransform))
                    let existingWorldAnchorEntity = privateComponent.worldAnchorEntity
                    
                    existingWorldAnchorEntity.children.forEach { child in
                        newWorldAnchorEntity.addChild(child)
                    }
                    
                    existingWorldAnchorEntity.parent?.addChild(newWorldAnchorEntity)
                    existingWorldAnchorEntity.removeFromParent()
                    entity.components.set(PrivateObjectToWorldComponent(worldAnchorEntity: newWorldAnchorEntity))
                }
            }
            else {
                // Move the contents from an object anchor to a world anchor
                let worldAnchorEntity = AnchorEntity(.world(transform: objectTransform))
                let privateComponent = PrivateObjectToWorldComponent(worldAnchorEntity: worldAnchorEntity)

                entity.children.forEach { child in
                    worldAnchorEntity.addChild(child)
                }
                
                entity.parent?.addChild(worldAnchorEntity)
                entity.components.set(privateComponent)
            }
        }
    }
}

Register the system and component in your app's initializer. Put this code in the file generated by Xcode that ends in "App".

init() {
    ObjectToWorldComponent.registerComponent()
    ObjectToWorldSystem.registerSystem()
}

Add code to your view to start a SpatialTrackingSession to obtain a person's permission to query the location of world and object anchor entities.

Important: to help protect people’s privacy, visionOS limits app access to cameras and other sensors in Apple Vision Pro. You need to add an NSWorldSensingUsageDescription to your app’s information property list to provide a usage description that explains how your app uses the data these sensors provide.

struct ImmersiveView: View {
    let spatialTrackingSession = SpatialTrackingSession()
    
    var body: some View {
        RealityView { content in
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)
            }
        }
        .task {
            // Start a spatial tracking session to obtain permission to query the object and world anchor transforms.
            let configuration = SpatialTrackingSession.Configuration(
                tracking: [.object, .world])
            await spatialTrackingSession.run(configuration)
        }
    }
}

Note: In Reality Composer Pro you can add ObjectToWorldComponent to any entity that has an object anchor component and it will adopt the desired behavior.

Thank you for the answer and for the great guidance. Great that it is possible, and that it will be easy to implement in Reality Composer pro, once it is set up.

I managed to create the Component in Reality Composer Pro, and added the code to ObjectToWorldComponent in Xcode

I run in to some problems after that. in NameApp I added the lines from your description, but I het to errors of "Cannot find 'ObjectToWorldComponent' in scope" and "Cannot find 'ObjectToWorldSystem' in scope"

Here is the code:

import SwiftUI


@main
struct ankerApp: App {

    
    @State private var appModel = AppModel()
    
    //Kode fra forum som flytter object anker til World Anker
    init() {
        ObjectToWorldComponent.registerComponent()
        ObjectToWorldSystem.registerSystem()
    }


    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(appModel)
        }
        .windowStyle(.volumetric)

        ImmersiveSpace(id: appModel.immersiveSpaceID) {
            ImmersiveView()
                .environment(appModel)
                .onAppear {
                    appModel.immersiveSpaceState = .open
                }
                .onDisappear {
                    appModel.immersiveSpaceState = .closed
                }
        }
        .immersionStyle(selection: .constant(.mixed), in: .mixed)
    }
}

And in ImmersiveView I get an error saying: "Result of call to 'run' is unused" That is next to the "await spatialTrackingSession.run(configuration)" line. Here is the code:

import RealityKit
import RealityKitContent

struct ImmersiveView: View {
    let spatialTrackingSession = SpatialTrackingSession()
    
    var body: some View {
        RealityView { content in
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)
            }
        }
        .task {
            // Start a spatial tracking session to obtain permission to query the object and world anchor transforms.
            let configuration = SpatialTrackingSession.Configuration(
                tracking: [.object, .world])
            await spatialTrackingSession.run(configuration)
        }
    }
}
#Preview(immersionStyle: .mixed) {
    ImmersiveView()
        .environment(AppModel())
}

I really hope to get some help with the last few issues.

Bedst regards.

@Ilskov

Answers inline.

"Cannot find 'ObjectToWorldComponent' in scope"

ObjectToWorldComponent should've been added for you by Reality Composer Pro when you created the component. In Xcode please look for Packages/RealityKitContent/Sources/RealityKitContent/ObjectToWorldComponent.swift and confirm it is named correctly. Sometimes people accidentally name it ObjectToWorldComponentComponent. It should contain the following code.

import RealityKit

public struct ObjectToWorldComponent: Component, Codable {

    public init() {
    }
}

"Cannot find 'ObjectToWorldSystem' in scope".

Did you copy/paste the above code for ObjectToWorldSystem into a new file? If so, make sure the file is part of your run target. An easy way to do this in Xcode is to delete and recreate the file.

  • Delete the file
  • Navigate to 'File -> New -> File From Template'
  • Select visionOS -> Swift File
  • Click Next
  • Important - make sure the checkbox in Targets is checked

More details on that in Configuring a new target in your project.

And in ImmersiveView I get an error saying: "Result of call to 'run' is unused"

That's a warning. To get rid of it change:

await spatialTrackingSession.run(configuration)

to

let _ = await spatialTrackingSession.run(configuration)

Arh, I put the system code in the ObjectToWorldComponent. I am not sure where that code goes. Do I make a new file from template (VisionOS Swift file) and put the code in there; and I that case does it go in the "top" of my project with the immersiveView and ProjectApp files, or should it go in RealityKitContent next to the ObjectToWorldComponent?

Would it be possible for you to make an empty vision app with just these components to help me understand where everything goes?

Thank you for the help and patience.

Update World Anchor using object anchor
 
 
Q