AR anchor shared across multiple immersive scenes

Hello,

I am currently working on an app that features multiple environments in which I combine Reality Composer Pro scenes with objects managed at runtime as well as make heavy use of RealityView attachments that modify the appearance of certain objects. Is it possible to keep track of an AR anchor when transitioning between immersive spaces?

About my app:

There are two main contexts/scenes in the app that the user progresses through. The first takes place in AR and is non-interactive and driven by a timeline animation. The second is in VR and allows the user to change materials of select models. Both scenes need to be placed relative to a real-life object that functions as an image anchor. Anchoring is necessary for visual purposes in AR context and it would be nice to use it in the VR context as well in order to provide passive haptics to the user.

If the user doesn't have access to the physical object, we make use of plane-based anchoring. Either way, we would like to keep the anchor's position across the scenes.

Answered by Vision Pro Engineer in 815462022

Hi @tomires

It was great connecting with you on Wednesday. If I understand correctly, you want to transition from mixed immersion to full immersion while maintaining the real-world position of an AR anchor. This is possible. Here's how.

  • Instead of closing the old space then opening the new one, conditionally render views in a single immersive space. The former changes the scene's origin and the latter does not.
  • Use a state variable to change the immersion style of the current immersive space.

Here's a code snippet. To create and run the app, create a new project then replace the generated code with the snippets below.

AppModel.swift

// Define an enum with entries for each immersive scene.
enum Scenes : String, CaseIterable, Identifiable {
    case red
    case green
    
    var id: Self { self }
}

//...

class AppModel {
    
    // Add a variable to store the current scene.
    var currentScene = Scenes.red
}

<YourAppName>App.swift

//...
// Set the immersion style based on the current scene.
let immersionStyle:ImmersionStyle = appModel.currentScene == .red ? .mixed : .full

ImmersiveSpace(id: appModel.immersiveSpaceID) {
    ImmersiveView()
 	//...
}
.immersionStyle(selection: .constant(immersionStyle), in: .mixed, .full)

ContentView.swift

// Display a picker to transition between scenes.
struct ContentView: View {
    @Environment(AppModel.self) private var appModel

    var body: some View {
        @Bindable var appModel = appModel

        VStack {
            Picker("Scene", selection: $appModel.currentScene) {
                ForEach(Scenes.allCases) {
                    Text($0.rawValue)
                }
            }

            ToggleImmersiveSpaceButton()
        }
        .padding()
    }
}

ImmersiveView.swift

struct ImmersiveView: View {
    @Environment(AppModel.self) private var appModel
    @State private var session = ARKitSession()
    @State private var anchorTransform:Transform?
    
    var body: some View {
        @Bindable var appModel = appModel
        
        VStack {
        	// Render the view for the current scene and pass it the anchor's transform.
            if let anchorTransform {
                switch appModel.currentScene {
                case .red:
                    RedView(anchorTransform: anchorTransform)
                case .green:
                    GreenView(anchorTransform: anchorTransform)
                }
            }
        }
        .task {
            guard ImageTrackingProvider.isSupported else {
                print("Image tracking is not supported.")
                return
            }
            
            let referenceImages = ReferenceImage.loadReferenceImages(inGroupNamed: "AR Resources")
            let imageTrackingProvider = ImageTrackingProvider(referenceImages: referenceImages)
            
            try? await session.run([imageTrackingProvider])
            
            for await update in imageTrackingProvider.anchorUpdates {
                
                if update.event == .added || update.event == .removed {
                    anchorTransform = Transform(matrix: update.anchor.originFromAnchorTransform)
                }
                
                // TODO handle remove
            }
        }

    }
}

struct RedView: View {
    let anchorTransform:Transform
    
    var body: some View {
        RealityView { content in
            let entity = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: false)])
            entity.transform = anchorTransform

            content.add(entity)
        }
    }
}

struct GreenView: View {
    let anchorTransform:Transform
    
    var body: some View {
        RealityView { content in
            let entity = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: false)])
            entity.transform = anchorTransform

            content.add(entity)
        }
    }
}

Accepted Answer

Hi @tomires

It was great connecting with you on Wednesday. If I understand correctly, you want to transition from mixed immersion to full immersion while maintaining the real-world position of an AR anchor. This is possible. Here's how.

  • Instead of closing the old space then opening the new one, conditionally render views in a single immersive space. The former changes the scene's origin and the latter does not.
  • Use a state variable to change the immersion style of the current immersive space.

Here's a code snippet. To create and run the app, create a new project then replace the generated code with the snippets below.

AppModel.swift

// Define an enum with entries for each immersive scene.
enum Scenes : String, CaseIterable, Identifiable {
    case red
    case green
    
    var id: Self { self }
}

//...

class AppModel {
    
    // Add a variable to store the current scene.
    var currentScene = Scenes.red
}

<YourAppName>App.swift

//...
// Set the immersion style based on the current scene.
let immersionStyle:ImmersionStyle = appModel.currentScene == .red ? .mixed : .full

ImmersiveSpace(id: appModel.immersiveSpaceID) {
    ImmersiveView()
 	//...
}
.immersionStyle(selection: .constant(immersionStyle), in: .mixed, .full)

ContentView.swift

// Display a picker to transition between scenes.
struct ContentView: View {
    @Environment(AppModel.self) private var appModel

    var body: some View {
        @Bindable var appModel = appModel

        VStack {
            Picker("Scene", selection: $appModel.currentScene) {
                ForEach(Scenes.allCases) {
                    Text($0.rawValue)
                }
            }

            ToggleImmersiveSpaceButton()
        }
        .padding()
    }
}

ImmersiveView.swift

struct ImmersiveView: View {
    @Environment(AppModel.self) private var appModel
    @State private var session = ARKitSession()
    @State private var anchorTransform:Transform?
    
    var body: some View {
        @Bindable var appModel = appModel
        
        VStack {
        	// Render the view for the current scene and pass it the anchor's transform.
            if let anchorTransform {
                switch appModel.currentScene {
                case .red:
                    RedView(anchorTransform: anchorTransform)
                case .green:
                    GreenView(anchorTransform: anchorTransform)
                }
            }
        }
        .task {
            guard ImageTrackingProvider.isSupported else {
                print("Image tracking is not supported.")
                return
            }
            
            let referenceImages = ReferenceImage.loadReferenceImages(inGroupNamed: "AR Resources")
            let imageTrackingProvider = ImageTrackingProvider(referenceImages: referenceImages)
            
            try? await session.run([imageTrackingProvider])
            
            for await update in imageTrackingProvider.anchorUpdates {
                
                if update.event == .added || update.event == .removed {
                    anchorTransform = Transform(matrix: update.anchor.originFromAnchorTransform)
                }
                
                // TODO handle remove
            }
        }

    }
}

struct RedView: View {
    let anchorTransform:Transform
    
    var body: some View {
        RealityView { content in
            let entity = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: false)])
            entity.transform = anchorTransform

            content.add(entity)
        }
    }
}

struct GreenView: View {
    let anchorTransform:Transform
    
    var body: some View {
        RealityView { content in
            let entity = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: false)])
            entity.transform = anchorTransform

            content.add(entity)
        }
    }
}

AR anchor shared across multiple immersive scenes
 
 
Q