Search code examples
swiftuirealitykitvisionos

USDZ availableAnimations and collision behaviors in Entity vs ModelEntity


I bought 2 USDZ models from Sketchfab. But I don't understand why the behavior is different.

The first model, I can use ModelEntity adding the object.model!.mesh.bounds.extents and storing the object.availableAnimations[0]

struct ImmrsiveView: View {

    @State var object: ModelEntity? = nil
    @State var animation: AnimationResource? = nil

    var body: some View {
        RealityView { content in
            do {
                object = try await ModelEntity(named: "speaker")

                if let object {

                    let objectBound = object.model!.mesh.bounds.extents
                    object.components.set(CollisionComponent(shapes: [.generateBox(size: objectBound)]))
                    object.components.set(InputTargetComponent())

                    animation = object.availableAnimations[0]
                    content.add(object)
                }
            }
            catch {
                print("Error loading the model")
            }
        }
    }
}

However, when I use the second USDZ file (by simply changing the entity name),

  1. The model doesn't load, to fix this, I must change the object type to Entity even though the loader is ModelEntity.load(named: "object") with a warning: No 'async' operations occur within 'await' expression.
  2. But then there's no .mesh.bounds.extents for Entity. The collision doesn't work
  3. As an Entity, now the availableAnimations[0] works!

So now the code becomes

struct ImmrsiveView: View {

    @State var object: Entity? = nil
    @State var animation: AnimationResource? = nil

    var body: some View {
        RealityView { content in
            do {
                object = try await ModelEntity.load(named: "object2")

                if let object {

                    // This doesn't work
                    // let objectBound = object.model!.mesh.bounds.extents
                    // object.components.set(CollisionComponent(shapes: [.generateBox(size: objectBound)]))
                    // object.components.set(InputTargetComponent())

                    animation = object.availableAnimations[0]
                    content.add(object)
                }
            }
            catch {
                print("Error loading the model")
            }
        }
    }
}

The questions are ... suppose there are different ways of loading USDZ files:

  1. How can I get rid of the warning?
  2. How can I fix the collision for the 2nd object?

xcode


Solution

  • You can use the ModelEntity(named:) initializer to load your character as model entity to assign a collision shape to it...

    import SwiftUI
    import RealityKit
    
    struct ContentView : View {
        @State var object = ModelEntity()
        @State var animation: AnimationResource? = nil
    
        var body: some View {
            RealityView { rvc in
                do {
                    object = try await ModelEntity(named: "Man")
                    object.position.z = -3.0
                    object.name = "Character"    
                    object.generateCollisionShapes(recursive: true)
                    object.components.set(InputTargetComponent())
                    animation = object.availableAnimations[0].repeat()
                    object.playAnimation(animation!)
                    rvc.add(object)                  
                    print(object.model?.mesh.bounds.extents as Any)
                } catch {
                    print("Error loading the model")
                }
            }
            .gesture(gesture)
        }       
        var gesture: some Gesture {
            TapGesture()
                .targetedToEntity(object)
                .onEnded {
                    print($0.entity.name)
                }
        }
    }
    

    ...however, the only "righteous" RealityKit's way is to find the ModelEntity within the hierarchical structure in your USDZ.

    struct ContentView : View {
        @State var object = Entity()
        @State var animation: AnimationResource? = nil
    
        var body: some View {
            RealityView { rvc in
                do {
                    object = try await Entity(named: "Man")
                    object.position.z = -3.0                  
                    // find the model entity's name in the hierarchy
                    // in my case it's "skin0"
                    print(object)
                    
                    let me = object.findEntity(named: "skin0") as! ModelEntity
                    me.generateCollisionShapes(recursive: true)
                    me.components.set(InputTargetComponent())
                    animation = me.availableAnimations[0].repeat()
                    me.playAnimation(animation!)
                    rvc.add(object)
                    print(me.model?.mesh.bounds.extents as Any)
                } catch {
                    print("Error loading the model")
                }
            }
            .gesture(gesture)
        }        
        var gesture: some Gesture {
            TapGesture()
                .targetedToEntity(object)
                .onEnded {
                    print($0.entity.name)
                }
        }
    }