The code below should translate touch coordinates into world coordinates for a SceneKit scene.
However, as illustrated by the output below, the point returned by unprojectPoint
returns effectively the same point no matter where you touch on the screen (iPhone 5s).
The class docs for unprojectPoint
suggest using Z values between 0 and 1, but using different values like 0.5 did not change the output for unprojectPoint
.
This SO post discusses how to set the depth value for unprojectPoint
, but setting the Z value to values greater than 1 (e.g., 15, 20) also did not change the output.
In both cases, the X and Y values return from unprojectPoint
effectively remained the same as well.
1) What is the right way to use unprojectPoint
?
2) How does unprojectPoint
account for camera rotations? For instance, if you moved the camera to (0, 20, 0) and rotated the camera down 90 degrees so it's facing the ground, how do you ensure the rotation is accounted for? If you set a depth of 20 and tapped on the origin, the desired return value from unprojectPoint
should be (0, 0, 0).
3) How do you get unprojectPoint
to return values in front of the camera (e.g., Z values are lower than the camera's Z value)
Code:
cameraNode.position = SCNVector3(x: 0, y: Float(0), z: Float(8))
func sceneViewTapped(recognizer: UITapGestureRecognizer) {
let point = recognizer.locationInView(sceneView)
let unprojectPoint = SCNVector3(x: Float(point.x), y: Float(point.y), z: 0.0)
let scenePos = sceneView.unprojectPoint(unprojectPoint)
print("2D point: \(point). 3D point: \(scenePos)")
}
Output:
2D point: (154.5, 169.5). 3D point: SCNVector3(x: -0.00111810782, y: 0.0232769605, z: 7.9000001)
2D point: (280.5, 252.0). 3D point: SCNVector3(x: 0.0244967155, y: 0.00650534919, z: 7.9000001)
2D point: (32.0, 181.0). 3D point: SCNVector3(x: -0.0260214079, y: 0.0209390987, z: 7.9000001)
2D point: (12.0, 505.0). 3D point: SCNVector3(x: -0.0300872531, y: -0.0449275821, z: 7.9000001)
2D point: (311.5, 12.5). 3D point: SCNVector3(x: 0.0307987742, y: 0.0551938377, z: 7.9000001)
2D point: (22.5, 88.0). 3D point: SCNVector3(x: -0.0279526841, y: 0.0398452766, z: 7.9000001)
2D point: (313.5, 358.0). 3D point: SCNVector3(x: 0.0312053617, y: -0.0150436237, z: 7.9000001)
2D point: (314.0, 507.0). 3D point: SCNVector3(x: 0.0313070044, y: -0.0453341678, z: 7.9000001)
2D point: (155.0, 360.5). 3D point: SCNVector3(x: -0.00101646129, y: -0.0155518558, z: 7.9000001)
As long as you use 0 and 1 the values do change for unprojectPoint.
Using 0 for the Z value represents the point on the near plane while using 1 yields the point on the far plane.
So to return a scene point that is an arbitrary distance from the camera, we developed the following function. We are new to SceneKit so please offer any edits or corrections!
Effectively, you define the ray/line between the near and far points then pick some point along the line.
private func touchPointToScenePoint(recognizer: UIGestureRecognizer) -> SCNVector3 {
// Get touch point
let touchPoint = recognizer.locationInView(sceneView)
// Compute near & far points
let nearVector = SCNVector3(x: Float(touchPoint.x), y: Float(touchPoint.y), z: 0)
let nearScenePoint = sceneView.unprojectPoint(nearVector)
let farVector = SCNVector3(x: Float(touchPoint.x), y: Float(touchPoint.y), z: 1)
let farScenePoint = sceneView.unprojectPoint(farVector)
// Compute view vector
let viewVector = SCNVector3(x: Float(farScenePoint.x - nearScenePoint.x), y: Float(farScenePoint.y - nearScenePoint.y), z: Float(farScenePoint.z - nearScenePoint.z))
// Normalize view vector
let vectorLength = sqrt(viewVector.x*viewVector.x + viewVector.y*viewVector.y + viewVector.z*viewVector.z)
let normalizedViewVector = SCNVector3(x: viewVector.x/vectorLength, y: viewVector.y/vectorLength, z: viewVector.z/vectorLength)
// Scale normalized vector to find scene point
let scale = Float(15)
let scenePoint = SCNVector3(x: normalizedViewVector.x*scale, y: normalizedViewVector.y*scale, z: normalizedViewVector.z*scale)
print("2D point: \(touchPoint). 3D point: \(nearScenePoint). Far point: \(farScenePoint). scene point: \(scenePoint)")
// Return <scenePoint>
return scenePoint
}