Rotating ModelEntities (without Gestures) Help

I'm building a proof of concept application leveraging the PlaneDetectionProvider to generate UI and interactive elements on a horizontal plane the user is looking at. I'm able to create a cube at the centroid of the plane and change it's location via position. However, I can't seem to rotate the cube programmatically and from this forum post in September I'm not sure if the modelEntity.move functionality is still bugged or the documentation is not up to date.

        if let planeCentroid = planeEntity.centroid {
            // Create a cube at the centroid
            let cubeMesh = MeshResource.generateBox(size: 0.1) // Create a cube with side length of 0.1 meters
            let cubeMaterial = SimpleMaterial(color: .blue, isMetallic: false)
            let cubeEntity = ModelEntity(mesh: cubeMesh, materials: [cubeMaterial])
            
            cubeEntity.position = planeCentroid
            cubeEntity.position.y += 0.3048
            planeEntity.addChild(cubeEntity)
            
            let rotationY = simd_quatf(angle: Float(45.0 * .pi/180.0), axis: SIMD3(x: 0, y: 1, z: 0))
            let cubeTransform = Transform(rotation: rotationY)
            cubeEntity.move(to: cubeTransform, relativeTo: planeEntity, duration: 5, timingFunction: .linear)
            

Ideally, I'd like to have the cube start/stop rotation when the user pinches on the plane mesh but I'd be happy just to see it rotate!

Hello @leland_bern

When an Entity is created and added to your scene, it is not immediately available to be animated. Internally, entity.move(to:relativeTo:duration:timingFunction) requires more setup than has occurred when you call this function on the same frame the Entity is created.

The answer in the stack overflow post you linked suggests using a task to delay the execution, but this is not quite correct. A more reliable solution is to subscribe to the SceneEvents.DidAddEntity event for the entity you want to animate. Here's an example of how you might do so:

_ = content.subscribe(to: SceneEvents.DidAddEntity.self, on: cubeEntity) {
    event in
    
    let rotationY = simd_quatf(angle: Float(45.0 * .pi/180.0), axis: SIMD3(x: 0, y: 1, z: 0))
    let cubeTransform = Transform(rotation: rotationY)
    event.entity.move(to: cubeTransform, relativeTo: planeEntity, duration: TimeInterval(10), timingFunction: .easeInOut)
}

In the above code, content is an instance of RealityViewContent like the one provided in the make closure of a RealityView. Let me know if you have any questions or would like assistance adapting this code to your context! Thanks for your question!

Thank you for the response! I'm new to Swift so I would definitely like assistance adapting the code. I see how it fits into the make closure of my RealityView. How can I provide the planeEntity and cubeEntity created in appModel.updatePlane to the DidAddEntity subscriber? Is it just as easy as adding the plane and cube entities to variables in appModel - appModel.currentPlaneRendered for example? This is my ImmersiveView View:

struct ImmersiveView: View {
    @Environment(AppModel.self) var appModel
    @State private var updateFacingPlaneTaskWorking: Task<Void, Never>? = nil

    var body: some View {
        RealityView { content in
            content.add(appModel.setupContentEntity())
            
            // Add the initial RealityKit content
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)
            }
            
            // Updates and renders the horizontal plane in front of the person at 10 Hz.
            updateFacingPlaneTaskWorking = run(appModel.updateFacingPlaneWorking, withFrequency: 10)

            // New content subscriber to the creation of a cubeEntity to rotate it after animation becomes available
            _ = content.subscribe(to: SceneEvents.DidAddEntity.self, on: cubeEntity) { event in
                let rotationY = simd_quatf(angle: Float(45.0 * .pi/180.0), axis: SIMD3(x: 0, y: 1, z: 0))
                let cubeTransform = Transform(rotation: rotationY)
                event.entity.move(to: cubeTransform, relativeTo: planeEntity, duration: TimeInterval(10), timingFunction: .easeInOut)
            }
        }
        .onDisappear {
            updateFacingPlaneTaskWorking?.cancel()
        }
        .task {
            await appModel.runARKitSession()
        }
        .task {
            await appModel.processPlaneDetectionUpdates()
        }
    }
}

I've adapted the code from the Room Tracking example for wall tracking into horizontal plane tracking with the PlaneDetectionProvider. The planeEntity and cubeEntity creation occurs in the task where I call await appModel.processPlaneDetectionUpdates().

In appModel I have the following functions:

    func processPlaneDetectionUpdates() async {
        for await anchorUpdate in planeData.anchorUpdates {
            switch anchorUpdate.event {
            case .added, .updated:
                await updatePlane(anchorUpdate.anchor)
            case .removed:
                await removePlane(anchorUpdate.anchor)
            }
        }
    }

    @MainActor
    func updatePlane(_ anchor: PlaneAnchor) async {
        // Get the transform matrix of the anchor
        let anchorTransform = anchor.originFromAnchorTransform
        
        // Convert the matrix into a RealityKit Transform
        var transform = Transform(matrix: anchorTransform)
        
        // Add 6 inches twice (0.1524 meters) to the y-axis
        transform.translation.y += 0.1524
        
        entityMap[anchor.id]?.transform = transform
        
        // getting anchor geometry
        let anchorGeometry = anchor.geometry
        guard let planeMeshResource = anchorGeometry.asMeshResource() else {
            return
        }
        let planeEntity = ModelEntity(mesh: planeMeshResource, materials: [whiteMaterial])
        planeEntity.transform = Transform(matrix: anchor.originFromAnchorTransform)
        planeEntity.name = anchor.classification.description
        
        // adding a collision component with a shape
        guard let shape = try? await ShapeResource.generateStaticMesh(from: planeMeshResource) else {
            logger.error("Failed to create ShapeResource from planeEntity geometry.")
            return
        }
        
        planeEntity.collision = CollisionComponent(shapes: [shape], isStatic: true)
        planeEntity.components.set(InputTargetComponent())
        planeEntity.components.set(HoverEffectComponent())
        
        if let planeCentroid = planeEntity.centroid {
            // Create a cube at the centroid
            let cubeMesh = MeshResource.generateBox(size: 0.1) // Create a cube with side length of 0.1 meters
            let cubeMaterial = SimpleMaterial(color: .blue, isMetallic: false)
            let cubeEntity = ModelEntity(mesh: cubeMesh, materials: [cubeMaterial])
            
            // Add a collision component to the cube
            let cubeShape = ShapeResource.generateBox(size: [0.1, 0.1, 0.1])
            cubeEntity.collision = CollisionComponent(shapes: [cubeShape])
            
            cubeEntity.position = planeCentroid
//            cubeEntity.position.y += 0.3048
            planeEntity.addChild(cubeEntity)
            
            // Check if the cube intersects the plane using bounding boxes
            if checkBoundingBoxOverlap(planeEntity: planeEntity, cubeEntity: cubeEntity) {
                print("Cube intersects with the plane!")
            } else {
                print("No intersection detected.")
            }
            
            // add plane classification text
            let font = UIFont.systemFont(ofSize: 0.2) // Adjust the font size to control text appearance
            let textMesh = MeshResource.generateText(
                anchor.classification.description,
                extrusionDepth: 0.06, // Adjust depth to control thickness
                font: font
            )
            let textEntity = ModelEntity(mesh: textMesh)
            textEntity.name = anchor.classification.description

            // Add white color material to the text
            textEntity.model?.materials = [whiteMaterial]

            // Add the BillboardComponent to the entity
            textEntity.components.set(BillboardComponent())
            textEntity.components.set(HoverEffectComponent())
            
            // Calculate the bounding box of the text mesh
            if let bounds = textEntity.model?.mesh.bounds {
                // Offset the text position to center it
                let offset = (bounds.max + bounds.min) / 2
                textEntity.position = planeCentroid + offset
            }
            planeEntity.addChild(textEntity)
        }
        
        colliderPlanesRoot.addChild(planeEntity)
    }
Rotating ModelEntities (without Gestures) Help
 
 
Q