Search code examples
swiftuirealitykitvisionos

Rotate multiple entities independently in RealityView


With a RealityView, an entity can be rotated using a gesture detector to the entire view:

RealityView { content in
    let loadedEntity = try await ModelEntity(named: modelName, 
                       in: RealityKitContent.realityKitContentBundle)
    // Add components to entity
    loadedEntity.generateCollisionShapes(recursive: true)
    loadedEntity.components.set(InputTargetComponent())

    loadedEntity.setParent(anchor)

    // Add the anchor which is the parent of the entity
    content.add(anchor)
  } .gesture(
      drag
  )

Question

This works for one model, but what if there are two models in the RealityView and each model should have a different rotation? How do we add a gesture to each model independently? Using Model3D?

This should not include building a scene in RealityComposerPro. We are talking about programmatically spawning objects in and allowing gestures.

Original post on figuring out rotation here


Solution

  • Rotating entities independently in RealityView

    I created two cubes in a Reality Composer Pro scene. And placed them at a distance of 50 cm from each other. If you do not need to build a scene in Reality Composer Pro from scratch, just add two primitives programmatically, or add two USDZ models with try? await Entity(named: String).

    enter image description here

    I've implemented .gesture() modifier and .targetedToEntity() modifier for each model.

    import SwiftUI
    import RealityKit
    import RealityKitContent
    

    struct ContentView: View {    
        @State var rotationA: Angle = .zero
        @State var rotationB: Angle = .zero
        @State var cubeA = Entity()
        @State var cubeB = Entity()
        
        var body: some View {            
            RealityView { content in
                if let scene = try? await Entity(named: "Scene", 
                                                    in: realityKitContentBundle) {
                    content.add(scene)
                    print(scene)
                }
            } update: { content in
                if let scene = content.entities.first {
                    Task {
                        cubeA = scene.findEntity(named: "Cube_A") ?? Entity()
                        cubeA.components.set(InputTargetComponent())
                        cubeA.generateCollisionShapes(recursive: false)
    
                        cubeB = scene.findEntity(named: "Cube_B") ?? Entity()
                        cubeB.components.set(InputTargetComponent())
                        cubeB.generateCollisionShapes(recursive: false)
                    }
                }
            }
            .gesture(
                DragGesture()
                    .targetedToEntity(cubeA)
                    .onChanged { _ in
                        rotationA.degrees += 5.0
                        let m1 = Transform(pitch: Float(rotationA.radians)).matrix
                        let m2 = Transform(yaw: Float(rotationA.radians)).matrix
                        cubeA.transform.matrix = matrix_multiply(m1, m2)
                        // Keep starting distance between models
                        cubeA.position.x = -0.25
                    }
            )
            .gesture(
                DragGesture()
                    .targetedToEntity(cubeB)
                    .onChanged { _ in
                        rotationB.degrees += 5.0
                        cubeB.transform = Transform(roll: Float(rotationB.radians))
                        // Keep starting distance between models
                        cubeB.position.x = 0.25
                    }
            )
        }
    }
    

    The scene hierarchy looks like this:

    enter image description here

    enter image description here