2

I have a custom usdz file (not create through code, but let's say a real chair!). I save this in a Entity.

Once I have it this is my code:

func updateUIView(_ uiView: ARView, context: Context) {
            
    if let modelEntity = model.modelEntity {
        
        print("\(model.modelName)")
        
        let anchorEntity = AnchorEntity(plane: .horizontal)
        
        anchorEntity.addChild(modelEntity.clone(recursive: true))
        
        uiView.scene.addAnchor(anchorEntity)
        
        // modelEntity.generateCollisionShapes(recursive: true) 
        // If we have multiple object, recursive true help to generate collision for all of them
        
        uiView.installGestures(.rotation, for: modelEntity as! Entity & HasCollision)
        
        uiView.debugOptions = .showPhysics
        
    } else {
        
        print("Unable to load modelEntity for \(model.modelName)")
        
    }
}

The problem here is that `"Argument type 'Entity' does not conform to expected type 'HasCollision'". So I cant add any gesture.

But I can't find any usefull resource to achieve my final goal. Is there any advice?

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
manubrio
  • 419
  • 6
  • 16

3 Answers3

8

I got some other situation: I needed to load the model from .usdz file and it should have an animation. But also I needed to have gestures like translation and rotation. The research directed me to the thread with the right answer. The code from it I'll show underneath, the main idea is "to nest the loaded entity which has the animations inside of a ModelEntity, and to then give that ModelEntity an appropriate CollisionComponent based on the loaded model's bounds." (c)

loadRequest = Entity.loadAsync(contentsOf: url)
    .sink(receiveCompletion: { status in
        print(status)
    }) { entity in
           
        // Scaling entity to a reasonable size
        entity.setScale(SIMD3(repeating: 0.01), relativeTo: nil)
           
        // Creating parent ModelEntity
        let parentEntity = ModelEntity()
        parentEntity.addChild(entity)
           
        // Anchoring the entity and adding it to the scene
        let anchor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: .zero))
        anchor.addChild(parentEntity)
        self.arView.scene.addAnchor(anchor)
           
        // Playing availableAnimations on repeat
        entity.availableAnimations.forEach { entity.playAnimation($0.repeat()) }
           
        // Add a collision component to the parentEntity with a rough shape and appropriate offset for the model that it contains
        let entityBounds = entity.visualBounds(relativeTo: parentEntity)
        parentEntity.collision = CollisionComponent(shapes: [ShapeResource.generateBox(size: entityBounds.extents).offsetBy(translation: entityBounds.center)])
                       
        // installing gestures for the parentEntity
        self.arView.installGestures(for: parentEntity)
    }
aheze
  • 24,434
  • 8
  • 68
  • 125
Hrabovskyi Oleksandr
  • 3,070
  • 2
  • 17
  • 36
  • Your answer exactly adds the required gestures to the entity but do you know how to add collision between the entity and real world objects like a wall or sofa? – Kawe Aug 19 '21 at 09:57
  • 1
    @KeyhanKamangar nice question, but now I haven't any suggestions. I think it's better to make a separate question for this – Hrabovskyi Oleksandr Aug 20 '21 at 14:47
  • I made a separate question for it: https://stackoverflow.com/questions/68857977/how-to-apply-collision-with-real-world-objects-to-a-3d-object-usdz-in-reality – Kawe Aug 23 '21 at 08:07
3

Use the forced form of downcasting (type casting) as! with Entity & HasCollision.

arView.installGestures([.rotation], for: modelEntity as! Entity & HasCollision)

or this way:

let entity = modelEntity as? Entity & HasCollision
arView.installGestures([.all], for: entity!)


A source instance method installGestures(_:for:) looks like this:

func installGestures(_ gestures: ARView.EntityGestures = .all,
                     for entity: HasCollision) -> [EntityGestureRecognizer]


Initial Settings in Reality Composer

Before compiling, in Reality Composer set physics = participates and motion type = fixed and accessibility = accessibility enabled for your model.

Full code version

import SwiftUI
import RealityKit

struct ARViewContainer: UIViewRepresentable {
    
    let boxAnchor = try! Experience.loadBox()
    
    func makeUIView(context: Context) -> ARView {
        
        let arView = ARView(frame: .zero)   
        arView.scene.anchors.append(boxAnchor)
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {
        
        if let modelEntity: Entity = boxAnchor.steelBox {    
            let anchorEntity = AnchorEntity(.plane(.vertical, 
                                             classification: .wall, 
                                             minimumBounds: [0.2, 0.2]))  
            anchorEntity.addChild(modelEntity.clone(recursive: true))    
            uiView.scene.addAnchor(anchorEntity)    
            modelEntity.generateCollisionShapes(recursive: true)    
            uiView.installGestures([.all], 
                               for: modelEntity as! Entity & HasCollision)    
            uiView.debugOptions = [.showPhysics]
        }
    }
}

P. S.

Also, this post will show you how raycasting works in conjunction with RealityKit gestures.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • 1
    Thank you very much for the answer. But at the moment this code crash with "Unexpectedly found nil while unwrapping an Optional value". My model format is usdz, is this correct? (Do I have to tick some options in realityConverter with my model?) – manubrio Oct 04 '20 at 09:15
  • 1
    Publish more of your code, please. And, at first, check the name of your usdz model. – Andy Jazz Oct 04 '20 at 09:19
  • 1
    Updated my question. My model comes from backend and I'm sure the name and the format are correct. Without gesture the placement works fine. Also: I'm working inside updateUIView because the project is made with SwiftUI. Can this be a problem? – manubrio Oct 04 '20 at 09:23
  • 1
    Me neither ... May I ask if you have any other solution? (modelEntity is of type Entiy) – manubrio Oct 04 '20 at 09:38
  • 1
    It's 100% that this issue comes from Reality Composer. Simple primitive cube works fine with gestures in Xcode 12. At first you should unable physics for your model in Reality Composer. – Andy Jazz Oct 04 '20 at 11:43
  • 1
    Thank you very much for your solution. I'm working in RealityConverter to export the model in .usdz. And sorry for my stupid question but I cant set the options you are talking about from here. Can I export an .usdz from RealityComposer? I'm sorry but I cant find the link between these two stuff – manubrio Oct 04 '20 at 12:47
  • 1
    You can put your model in Reality Composer and export a project as `.rcproject`. After it almost all scene's components are accessible thru RealityKit's API. If you have any other questions, please publish them as another SO posts. – Andy Jazz Oct 04 '20 at 12:51
  • By the way I'm trying this approach with a correct model and i'm still not able to cast Entity to HasCollision (I've converted my usdz model to .reality with all the necessary phisics). I think you misunderstand my question since i'm working with custom model and not the box from the default scene. Do you have any advice? @AndyFedoroff – manubrio Oct 24 '20 at 14:18
1

The problem is that you are trying to give the ModelEntity an ability it does not posses (It does not have a collision handler).

You need to create an Entity yourself, that will conform to HasCollision.

I would try something like this:

import RealityKit

class MyEntity: Entity, HasAnchoring, HasModel, HasCollision {

}

func updateUIView(_ uiView: ARView, context: Context) {
    // This is simply to create a dummy modelEntity
    let plane = MeshResource.generatePlane(width: 0.1, depth: 0.1)
    let texture = MaterialColorParameter.color(UIColor.red)
    var material = SimpleMaterial()
    material.baseColor = texture
    let modelEntity = ModelEntity(mesh: plane, materials: [material])
      
    // This is the class we have wrapping the model
    let myEntity = MyEntity()
    myEntity.addChild(modelEntity)
    
    // Now, we add the collision component
    let boxShape = ShapeResource.generateBox(width: 0.1, height: 0.1, depth: 0.1)
    let boxShapeCollisionComponent = CollisionComponent (
      shapes: [boxShape],
      mode: .trigger,
      filter: .default
    )
    myEntity.collision = boxShapeCollisionComponent
    // Or, you could of called myEntity.generateCollisionShapes(recursive: true)
    
    // Last thing, lets put this plane, with a box collision component,
    // right in front of us
    myEntity.transform = Transform.identity
    myEntity.transform.translation.z = myEntity.transform.translation.z - 0.3
    uiView.scene.addAnchor(myEntity)
    
    uiView.installGestures(.rotation, for: myEntity)
    
    uiView.debugOptions = .showPhysics
}
YanivH
  • 539
  • 4
  • 18