RotateGesture3D auto constrained to axis

Hi,

On visionOS to manage entity rotation we can rely on RotateGesture3D. We can even with the constrainedToAxis parameter authorize only rotation on an x, y or z axis or even make combinations.

What I want to know is if it is possible to constrain the rotation on axis automatically. Let me explain, the functionality that I would like to implement is to constrain the rotation on an axis only once the user has started his gesture. The initial gesture the user makes should let us know which axis they want to rotate on.

This would be equivalent to activating a constraint automatically on one of the axes, as if we were defining the gesture on one of the axes.

RotateGesture3D(constrainedToAxis: .x)
RotateGesture3D(constrainedToAxis: .y)
RotateGesture3D(constrainedToAxis: .z)

Is it possible to do this?

If so, what would be the best way to do it?

A code example would be greatly appreciated.

Regards

Tof

Answered by Vision Pro Engineer in 822914022

Hi @ZeTof ,

There's no way to do this currently (at least that I could find!) so I'd really appreciate it if you filed a enhancement report at https://feedbackassistant.apple.com explaining your use case.

My attempt had been to chance a state variable that sets the axis to constrain while rotating, but since the RotateGesture onChanged closure is the only thing that is called as you rotate, it won't update the axis of constraint.

One idea that I can pitch for you to try is conditionally setting the rotation of each angle to constrain the axes as it's being updated:

 .gesture(RotateGesture3D()
            .targetedToAnyEntity()
            .onChanged({ value in
                
                let entity = value.entity
// state is the class shown below
                if !state.isRotating {
                    state.isRotating = true
                    state.startOrientation = .init(entity.orientation(relativeTo: nil))
                }
                let rotation = value.rotation
                var zRot = -rotation.axis.z
                var xRot = -rotation.axis.x
                var yRot = rotation.axis.y
                // conditionally set the rotation axes to 0 here based on the angle of rotation
                //...
                let flippedRotation = Rotation3D(angle: rotation.angle,
                                                 axis: RotationAxis3D(x: xRot,
                                                                      y: yRot,
                                                                      z: zRot))
                let newOrientation = state.startOrientation.rotated(by: flippedRotation)
                entity.setOrientation(.init(newOrientation), relativeTo: nil)
            })
                .onEnded({ value in
                    state.isRotating = false
                }))
public class EntityGestureState {
    
    /// The entity currently being dragged if a gesture is in progress.
    var targetedEntity: Entity?
    
   var currAxis: RotationAxis3D?
    
    // MARK: - Rotation
    
    /// The starting rotation value.
    var startOrientation = Rotation3D.identity
    
    /// Marks whether the app is currently handling a rotation gesture.
    var isRotating = false

}
Accepted Answer

Hi @ZeTof ,

There's no way to do this currently (at least that I could find!) so I'd really appreciate it if you filed a enhancement report at https://feedbackassistant.apple.com explaining your use case.

My attempt had been to chance a state variable that sets the axis to constrain while rotating, but since the RotateGesture onChanged closure is the only thing that is called as you rotate, it won't update the axis of constraint.

One idea that I can pitch for you to try is conditionally setting the rotation of each angle to constrain the axes as it's being updated:

 .gesture(RotateGesture3D()
            .targetedToAnyEntity()
            .onChanged({ value in
                
                let entity = value.entity
// state is the class shown below
                if !state.isRotating {
                    state.isRotating = true
                    state.startOrientation = .init(entity.orientation(relativeTo: nil))
                }
                let rotation = value.rotation
                var zRot = -rotation.axis.z
                var xRot = -rotation.axis.x
                var yRot = rotation.axis.y
                // conditionally set the rotation axes to 0 here based on the angle of rotation
                //...
                let flippedRotation = Rotation3D(angle: rotation.angle,
                                                 axis: RotationAxis3D(x: xRot,
                                                                      y: yRot,
                                                                      z: zRot))
                let newOrientation = state.startOrientation.rotated(by: flippedRotation)
                entity.setOrientation(.init(newOrientation), relativeTo: nil)
            })
                .onEnded({ value in
                    state.isRotating = false
                }))
public class EntityGestureState {
    
    /// The entity currently being dragged if a gesture is in progress.
    var targetedEntity: Entity?
    
   var currAxis: RotationAxis3D?
    
    // MARK: - Rotation
    
    /// The starting rotation value.
    var startOrientation = Rotation3D.identity
    
    /// Marks whether the app is currently handling a rotation gesture.
    var isRotating = false

}

Hi,

First of all thank you for taking the time to answer me it is very much appreciated.

Thanks for the suggestion I will try to see that.

For my part I also thought to create a custom gesture that would allow to identify with certainty on which axis the user wants to go.

Another possibility would be to do as when we are in Reality Composer Pro and to display the axes on the object to allow the user to select the axis he wants. But I don't really know how to do that. I looked for examples but couldn't find any.

For feedbackassistant I have used it in the past and never had any feedback. So I doubt it is really very useful.

Regards

Tof

Hey @ZeTof ,

I think a custom gesture would work! you'd be able to track the direction of the hand and conditionally execute a movement/constrain the movement. You would then be limited to an immersive space though, in case that changes anything for you.

Here's a small snippet about how to add one of the axes, it should help in figuring out all of them

struct ImmersiveView: View {

    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            let cube = ModelEntity(mesh: .generateBox(size: 0.1), materials: [SimpleMaterial(color: .red, isMetallic: true)])
            content.add(cube)
            // Move the cube up to be easier to see in the scene.
            cube.position.y = 1
            
            // z axis cylinder
            let zAxisEntity = ModelEntity(mesh: .generateCylinder(height: 0.5, radius: 0.001), materials: [SimpleMaterial(color: .green, isMetallic: false)])
            // angle
            let zAxis = Float.pi / 2
            
            // set the transform
            zAxisEntity.transform.rotation *= simd_quatf(angle: zAxis, axis: .init(x: 1, y: 0, z: 0))
            cube.addChild(zAxisEntity)
            // add it in the center of the entity
            zAxisEntity.setPosition([0, 0, 0], relativeTo: cube)
        }
    }
}

This creates a cube and adds a cylinder with a z axis entity going through it. you can create a few more axis entities and do the same thing, with varying angles.

RotateGesture3D auto constrained to axis
 
 
Q