Shan Robertson
Shan Robertson

Reputation: 2742

Scenekit rotate node to face tapped point

I'm using the default scenekit game project to try and implement some tap controls to fly the ship around, the default one that comes with that project. What i'm trying to figure out, is how to rotate my ship's nose to face the point that is tapped on screen. I have been following any and all examples of this i've seen and have come up with this:

let point:CGPoint = sender.location(in: sender.view)
let deltaY:Float = Float(point.y) - ship.position.y
let deltaX:Float = Float(point.x) - ship.position.x
let degreesToRadians = Float.pi / 180
let angle = atan2(deltaY, deltaX) + (90 * degreesToRadians)
ship.rotation = SCNVector4(0,1,0,angle)

A few things to note:

What happens when I tap my finger around the ship in a circle is the ship rotates in a 90degree arc from 0deg - 90deg. Just incase that isn't clear (or correct), it's moving between east and north no matter where i am tapping on the screen.

one last thing to note. My camera is locked to an overhead view, so i only want to be rotating the Y axis, here is a quick screenshot of the orientation and first load position: first load position

If you have any tips, ideas or knowledge to send my way that would be great. This problem has me stumped.

Upvotes: 1

Views: 1219

Answers (1)

Craig Siemens
Craig Siemens

Reputation: 13296

Your getting close, theres just a couple conversion steps your missing.

First convert the location of the tap in the views coordinates, this is the same as your code, just different names.

let viewPoint = gestureRecognize.location(in: scnView)

Since the view is 2D but the SCNScene is 3D, you need to convert into scene coordinates using scnView.unprojectPoint.

let camera = cameraNode.camera
let distance = camera.zFar - camera.zNear / cameraNode.position.y
let scenePoint = scnView.unprojectPoint(SCNVector3(viewPoint.x, viewPoint.y, CGFloat(distance)))

Then calculate the angle. The important thing in your case is that you're looking straight down so the axises you want to work with are X and Z. Also, positive X is on the right but positive Z is pointing down which needs to be taken into account.

let deltaZ = scenePoint.z - ship.position.z
let deltaX = scenePoint.x - ship.position.x
let angle = atan2(deltaZ, deltaX) - .pi / 2
ship.rotation = SCNVector4(0, -1, 0, angle)

And that should do it. Also not that if you camera was rotated by any amount that isn't a multiple of 90 degrees, the 2D -> 3D conversion and angle calculation will be much more complicated since it isn't as simple as treating one axis as another one.


Edit - Rotation across 0/360 deg boundary

You'll need to check wether rotating clockwise or counter clockwise will require more movement and update the angle.

if abs(angle - .pi * 2 - ship.rotation.w) < abs(angle - ship.rotation.w) {
    angle -= .pi * 2
}

Also, I think Quaternions may handle this automatically but I'm not familiar enough with them to post a solution.

Upvotes: 3

Related Questions