Search code examples
swiftaugmented-realityarkitrealitykitreality-composer

Alternative for findEntity(named:) method to find a specific model in RealityKit?


So I asked a question a couple of days ago which Andy Jazz kindly answered. So I have been playing with the answer, and although I was able to implement a solution, it is incomplete because the method he used, was findEntity(named:) using RealityKit. While I used Reality Composer on my own answer.

Problem #1: findEntity(named:) method doesn't work on Reality Composer when you have multiple objects. So I arrived at the realization the using the findEntity(named:) method doesn't work. Because all the objects within the scene are named "simpBld_root", it doesn't work to trigger what I want to trigger.

Problem #2: When using the parenting notation, I am doing a trial and error because I don't really understand what is what. How can I call each object? This is also a consequence of my own inability to correctly understand WHAT to call from Reality Composer because I don't think I really understand how the parenting notation applies in reality composer. The picture below is the Scene I am working printed on the console.

enter image description here

Below is an example I made work, where instead of Andy's solution for findEntity(named: ) I used children[0]

This is the code I tried, when using the findEntity() method that did NOT work:

let redBoardModelEntity =  cubesAnchor.redBoard?.findEntity(named: "simpBld_root") as! ModelEntity
let greenCubeModelEntity =  cubesAnchor.greenCube?.findEntity(named: "simpBld_root") as! ModelEntity
let blueCubeModelEntity =  cubesAnchor.blueCube?.findEntity(named: "simpBld_root") as! ModelEntity

And this is what I ended up with, that worked, meaning, it showed all the objects and the entire thing dragged together. [See pic below, followed by code]

enter image description here

import UIKit
import RealityKit

class ViewController: UIViewController {
    @IBOutlet var arView: ARView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
                
        let boxAnchor = try! Experience.loadBox()
        arView.scene.anchors.append(boxAnchor)            
    
        let cubesAnchor = try! Experience.loadCubes()
        arView.scene.anchors.append(cubesAnchor)
        print (cubesAnchor)
        cubesAnchor.actions.behavior.onAction = handleTapOnEntity(_:)            
        
        let redModel =  cubesAnchor.children[0].children[0]
                  
        let group = ModelEntity() as ModelEntity & HasCollision
        group.addChild(redModel)
        // group.addChild(blueCubeModelEntity)
        // group.addChild(greenCubeModelEntity)
        
        group.generateCollisionShapes(recursive: false)
        self.arView.installGestures(.all, for: group)
        
        let shape = ShapeResource.generateBox(width: 1, height: 1, depth: 1)
        let collision = CollisionComponent(shapes: [shape],
                                             mode: .trigger,                                               
                                           filter: .sensor)
        group.components.set(collision)
        
        let anchor = AnchorEntity()
        anchor.addChild(group)
        anchor.scale = [5,5,5]
        arView.scene.anchors.append(anchor)            
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0){
            // self.arView.scene.anchors.removeAll()
            let coneAnchor = try! Experience.loadCone()
            self.arView.scene.anchors.append(coneAnchor)
            coneAnchor.actions.behavior.onAction = handleTapOnEntity(_:)                    
            // print(cubesAnchor)                
        }                          
        
        boxAnchor.actions.behavior.onAction = handleTapOnEntity(_:)
            
        func handleTapOnEntity(_ entity: Entity?){
            guard let entity = entity else { return }
            
           // self.myEntity = entity
           // self.myEntity?.isEnabled = false    
        }            
    }
}

Solution

  • Traversing children entities (nodes)

    According to diagram, your models are called greenCube, redBoard and blueCube respectively. Thus, the simplest way to get any ModelEntity from Reality Composer's scene hierarchy is to apply findEntity(named:) method followed by access to its sub-children.

    let model = cubesAnchor.findEntity(named: "greenCube")?.children[0] as! ModelEntity
    

    ...or, thanks to the Reality Composer's naming scheme:

    let model = cubesAnchor.greenCube?.children[0] as! ModelEntity
    

    Names greenCube, redBoard and blueCube are entities' names. All three models have a name simpBld_root. So, you can rename each ModelEntity if you like.

    model.name = "Green_Cube_Model"
    
    print(cubesAnchor.findEntity(named: "greenCube")!)
    

    Result in Xcode's console:

    ▿ 'greenCube' : Entity, children: 1
      ⟐ Transform
      ⟐ SynchronizationComponent
      ▿ 'Green_Cube_Model' : ModelEntity         // New name
        ⟐ PhysicsBodyComponent
        ⟐ Transform
        ⟐ SynchronizationComponent
        ⟐ CollisionComponent
        ⟐ ModelComponent
    

    Now you are able to get each ModelEntity via its unique name:

    let green = cubesAnchor.findEntity(named: "Green_Cube_Model") as! ModelEntity
    

    Or you can go through the chain of children.


    Here's my code:

    import UIKit
    import RealityKit
        
    class ViewController: UIViewController {
        
        @IBOutlet var arView: ARView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let scene = try! Experience.loadTwoBoxes()
            
            // MODEL 001 
            let model001 = scene.findEntity(named: "redCube")?.children[0] as! ModelEntity & HasPhysicsBody
            model001.position.y = 1.0
            model001.physicsBody = .init()
            model001.generateCollisionShapes(recursive: false)
            
            // MODEL 002       
            let forRenaming: Entity = scene.findEntity(named: "blueCube")!
            forRenaming.children[0].name = "Blue_Cube_Model"
            print(forRenaming)
        
            let model002 = scene.findEntity(named: "Blue_Cube_Model") as! ModelEntity & HasPhysicsBody
            model002.position.y = 1.0
            model002.physicsBody = .init()
            model002.generateCollisionShapes(recursive: false)
    
            arView.scene.anchors.append(scene)
        }
    }