swiftuirealitykitusdzvisionosreality-composer-pro

Is it possible to change usdz objects inside a scene programmatically?


Let's say I've created a scene with 3 models inside side by side. Now upon user interaction, I'd like to change these models to another model (that is also in the same reality composer pro project). Is that possible? How can one do that?

One way I can think of is to just load all the individual models in RealityView and then just toggle the opacity to show/hide the models. But this doesn't seem like the right way for performance/memory reasons.

How do you swap in and out usdz models?

Right now I load a scene using this code:

struct VolumeView: View {
@State private var show = false
@State var car: Entity?
@State var plane: Entity?

var body: some View {
    VStack {
        Toggle("Show/Hide", isOn: $show)
            .onChange(of: show) { _, newValue in
                if newValue {
                } else {
                }
            }.padding()

        RealityView { content in

            await plane = try? Entity(named: "Plane",
                                      in: realityKitContentBundle)
            await car = try? Entity(named: "Car",
                                    in: realityKitContentBundle)
            if let plane = plane, let car = car {
                content.add(car)
                content.add(plane)
            }
        } update: { content in
            if show {
                /// show new model somehow
            } else {
                /// revert to old model someohow
            }
        }
    }
}

}


Solution

  • You haven't provided much information about behavior but update would get called each time show is changed so you would just have to removeFromParent and addChild or content.remove and content.add.

    Which is right depends on if you want to swap children from the parent or if you want to work with content directly.

    Here is some swapping logic.

    extension Entity {
        func swap(_ first: String, name second: String, isFirst: Bool) async throws {
            switch isFirst {
            case true:
                switch self.findEntity(named: first){
                case .none:
                    children.first?.removeFromParent()
                    let entity = try await Entity(named: first)
                    self.addChild(entity)
                case .some(_):
                    break //Do nothing
                }
                
            case false:
                switch self.findEntity(named: second){
                case .none:
                    children.first?.removeFromParent()
                    let entity = try await Entity(named: second)
                    self.addChild(entity)
                case .some(_):
                    break //Do nothing
                }
            }
        }
    }
    
    import SwiftUI
    import RealityKit
    struct SwapEntityView: View {
        
        @State private var show = false
        @State private var parent1: Entity = .init()
        @State private var parent2: Entity = .init()
        
        var body: some View {
            VStack {
                Toggle("Show/Hide", isOn: $show)
    
                RealityView { content in
                        await swapParent1(show: show)
                        await swapParent2(show: show)
                        
                        content.add(parent1)
                        content.add(parent2)
                } update: { content in
                    Task { @MainActor in
                        await swapParent1(show: show)
                        await swapParent2(show: show)
                    }
                }
            }
        }
        
        func swapParent1(show: Bool) async {
            do {
                try await parent1.swap("Earth", name: "Mars", isFirst: show)
            } catch {
                print(error)
            }
        }
        func swapParent2(show: Bool) async {
            do {
            try await parent1.swap("RocketToy1", name: "ToyBiplane", isFirst: show)
            } catch {
                print(error)
            }
        }
    }
    

    And the content approach is as follows.

    import SwiftUI
    import RealityKit
    struct AddRemoveView: View {
        
        @State private var show = false
        @State private var planet: Entity?
        @State private var plane: Entity?
        
        var body: some View {
            VStack {
                Toggle("Show/Hide", isOn: $show)
                
                RealityView { content in
                    do {
                        planet = try await Entity(named: "Earth")
                        plane = try await Entity(named: "ToyBiplane")
                        guard let plane, let planet else {
                            return
                        }
                        try show(show: show, content: content)
                    } catch {
                        print(error)
                    }
                } update: { content in
                    do {
                        try show(show: show, content: content)
                    } catch {
                        print(error)
                    }
                    
                }
            }
        }
        func show(show: Bool, content: RealityViewContent) throws {
            guard let plane, let planet else {return}
            switch show {
            case true:
                content.remove(plane)
                content.add(planet)
            case false:
                content.remove(planet)
                content.add(plane)
            }
        }
    }
    
    #Preview {
        AddRemoveView()
    }
    

    You can also create any combination of these.