Search code examples
swiftarkitrealitykit

Custom Pan gesture handler for a model rotation


I would like to allow users to rotate the model with a pan gesture. The rotation should follow the finger movement. The possible movement could look like https://www.youtube.com/watch?v=50EjlzVsQM8 but with rotation along the z-axis as well.

I implemented a pan gesture handler:

@objc private func handlePan(recognizer: UIPanGestureRecognizer) {
    let currentTouchPoint = recognizer.location(in: self.arView)
    guard let ray = self.arView.ray(through: currentTouchPoint) else {
        return
    }
    anchorEntity.transform.rotation = .init(angle: .pi, axis: ray.direction)
}

but now, there are several problems with this code:

  1. Since the angle is set to PI, the gesture flips the model completely and I can rotate it from there which is not what I want. But if I set it to .zero, I can’t rotate it at all.
  2. It requires a lot of effort to rotate in a sense that I need to do very long finger movements and it is impossible to do a full rotation / move it back to the start point.

I believe I need to somehow adapt the angle / direction, but not sure where to start, I am not really familiar with the 3D transformations. Any help would be appreciated.


Solution

  • Since the 2D pan gesture has just x/y coordinates, with pan, you're able to control the rotation of your model around definite pair of axes, such as x/y or y/z. Here's the code:

    import RealityKit
    import ARKit
    
    class ViewController : UIViewController {
        @IBOutlet var arView: ARView!
        let boxPrim = ModelEntity(mesh: .generateBox(size: 0.75))
        var previousPoint = CGPoint()
        let anchor = AnchorEntity()
        var deg: Float = .zero
        
        @objc func panning(recognizer: UIPanGestureRecognizer) {
            deg += 5.0
            var currentPoint = recognizer.location(in: arView)
            let rad = deg * .pi / 180
            
            if currentPoint.x > previousPoint.x {
                boxPrim.transform = Transform(yaw: rad * 1.5)  // Y
                currentPoint.y = 0
            }
            if currentPoint.x < previousPoint.x {
                boxPrim.transform = Transform(yaw: -rad * 1.5) // Y
                currentPoint.y = 0
            }
            if currentPoint.y > previousPoint.y {
                boxPrim.transform = Transform(pitch: rad)      // X
            }
            if currentPoint.y < previousPoint.y {
                boxPrim.transform = Transform(pitch: -rad)     // X
            }
            previousPoint = currentPoint
        }
        func gestureRecognizer() {
            let recognizer = UIPanGestureRecognizer(target: self,
                                                    action: #selector(panning))
            arView.addGestureRecognizer(recognizer)
        }
        override func viewDidLoad() {
            super.viewDidLoad()
            anchor.addChild(boxPrim)
            arView.scene.addAnchor(anchor)
            self.gestureRecognizer()
        }
    }