Search code examples
swiftrotationscenekitdegreesradians

How to rotate and lock the rotation of a SCNNode by degrees?


I need to rotate a model that can be freely rotated, to exact degrees, regardless of how many times it's been rotated.

I have a UIPanGestureRecognizer that is rotating freely a 3D model around the Y axis. However I'm struggling to get it to lock to a integer degree when panning is stopped, and I'm struggling with being able to know it's rotation in degrees from 0-359.

let translation = recognizer.translation(in: self.view)

var newAngleY = Double(translation.x) * (Double.pi) / 180.0
newAngleY += self.currentAngle

self.shipNode?.eulerAngles.y = Float(newAngleY)

if (recognizer.state == .ended)
{
   self.currentAngle = newAngleY
}

It rotates freely, but all attempts for locking to the closest exact degree, and being able to 'know' it's rotational degree in a value from 0-359.

I know that:

let degrees = newAngleY * ( 180 / Double.pi)

And I know that if degrees > 360 then -= 360 (pseudo code)

However, whilst the UIPanGestureRecognizer is doing it's thing, these checks seem to fail and I don't know why. Is it because when it's still being panned, you can't edit the private properties of the ViewController?


Solution

  • You can edit the value while the gesture is occurring.

    Quite a few options, so this seems the simplest to start with:

    You could try only applying euler when the state changes AND only when .x > .x * (some value, such as 1.1). This would provide a more "snap to" kind of approach, something like:

     var currentLocation = CGPoint.zero
     var beginLocation = CGPoint.zero
    
     @objc func handlePan(recognizer: UIPanGestureRecognizer) {
        currentLocation = recognizer.location(in: gameScene)
    
        var newAngleY = Double(translation.x) * (Double.pi) / 180.0
        newAngleY += self.currentAngle
    
        switch recognizer.state
        {
         case UIGestureRecognizer.State.began: break
         case UIGestureRecognizer.State.changed:
             if(currentLocation.x > beginLocation.x * 1.1)
             {
               gNodes.bnode.eulerAngles.y = Float(newAngleY)
               beginLocation.x = currentLocation.x
             }
             if(currentLocation.x < beginLocation.x * 0.9) { .etc. }
             break
             case UIGestureRecognizer.State.ended:
               gNodes.bnode.eulerAngles.y = Float(newAngleY)
               break
        }
    }
    

    Then you could switch to an SCNAction (changing your math) to give more control, such as

    let vAction = SCNAction.rotateTo(x: 0, y: vAmount, z: 0, duration: 0)
    bnode.runAction(vAction)