How to rotate a ModelEntity in visionOS?
Asked Answered
C

2

1

When loading a model as a Model3D object, you can add a gesture modifier like so:

 Model3D(named: model_name, bundle: realityKitContentBundle){ model in
             model
             .resizable()
             .aspectRatio(contentMode: .fit)
} ...
.gesture(DragGesture()
.onChanged { 
...
}

Another example is shown on this post.

How does somebody add a gesture such as DragGesture() to a loaded entity? The entity likely needs InputTarget and Collision components added to work:

let loadedEntity = try await ModelEntity(named: modelName, in: RealityKitContent.realityKitContentBundle)
                
// Add components to entity
loadedEntity.generateCollisionShapes(recursive: true)
loadedEntity.components.set(InputTargetComponent())
Cinelli answered 30/7, 2023 at 1:8 Comment(0)
H
1

Rotating 3D view with a Drag Gesture

If a 3D model is alone in SwiftUI view, you can rotate the whole view with a drag gesture. However, there may be several models in a scene. So, if you prefer to rotate each model of the scene separately from the others, read this post.

When you use RealityView, you get a unique functionality that is not available in the Model3D view (I mean the ability to programmatically change a scene's structure, or add anchors and components). Here's my code:

enter image description here

import SwiftUI
import RealityKit

struct ContentView: View {

    @State var isDragging: Bool = false
    @State var rotation: Angle = .zero
    
    var drag: some Gesture {
        DragGesture()
            .onChanged { _ in
                isDragging = true
                rotation.degrees += 5.0
            }
            .onEnded { _ in
                isDragging = false
            }
    }   
    var body: some View {
        RealityView { content in
            let cube = ModelEntity(mesh: .generateBox(size: 0.25))
            cube.generateCollisionShapes(recursive: false)
            cube.components.set(InputTargetComponent())
            content.add(cube)
        }
        .gesture(drag)
        .rotation3DEffect(rotation, axis: .xy)
    }
}
#Preview {
    ContentView()
}
Homager answered 30/7, 2023 at 8:32 Comment(6)
Are you saying that if multiple objects need to be added to a scene with independent rotation gestures, Model3D should be used instead of RealityView?Cinelli
Nope. Using Model3D views, if you need to rotate 2 models independently, you must use two Model3D views with isDraggingA and isDraggingB, rotationA and rotationB, dragA and dragB properties. But it's inconveniently.Homager
The most convenient way is to use RealityView in which 2 (or more) models are part of the hierarchical structure of this RealityView.Homager
This seems odd as Apple would want for complex scenes (one RealityView) where multiple objects have independent interactions. Yet, the examples I am seeing everywhere, including in documentation, point to rotating the entire view instead of single entities within the view.Cinelli
I posted another question where we can dive further into this topic #76823688Cinelli
This works fine if the object is positioned at the world origin. But how can I make this work if the object has been translated to another position? In this case, it still rotates around the world origin as the pivot point, which is not what I want. I need a local rotation instead.Ringmaster
C
0

I was able to accomplish the rotation by attaching a gesture to the RealityView and accessing the child entity through its anchor. However, I find this to not be the best solution under the idea that there are multiple objects within the same RealityView.

There must be a better way to attach gestures object-by-object.

RealityView { content in
    do {
        ...
    } catch {
        print("oopsie")
    }
}.gesture(
    DragGesture()
        .onChanged { value in
            // Calculate rotation angle
            let angle = sqrt(pow(value.translation.width, 2) + pow(value.translation.height, 2))
            rotation = Angle(degrees: Double(angle)) 
            // Calculate rotation axis
            let axisX = -value.translation.height / CGFloat(angle)
            let axisY = value.translation.width / CGFloat(angle)
            rotationAxis = (x: axisX, y: axisY, z: 0)
            // Apply rotation to loadedEntity
            let quaternion = simd_quatf(
                angle: Float(rotation.radians),
                axis: SIMD3<Float>(
                    x: Float(rotationAxis.x),
                    y: Float(rotationAxis.y),
                    z: Float(rotationAxis.z)
                )
            )
            anchor.children[0].orientation = quaternion
        }
)
Cinelli answered 30/7, 2023 at 1:43 Comment(1)
I assume rotationAxis is a let, but what's anchor here please?Zela

© 2022 - 2024 — McMap. All rights reserved.