Scenekit - Get Direction of Camera

SceneKit – Get direction of camera

You can create an SCNNode that place it in worldFront property to get a vector with the x, y, and z direction.

Another way you could do it is like how this project did it:

// Credit to https://github.com/farice/ARShooter

func getUserVector() -> (SCNVector3, SCNVector3) { // (direction, position)
if let frame = self.sceneView.session.currentFrame {
let mat = SCNMatrix4(frame.camera.transform) // 4x4 transform matrix describing camera in world space
let dir = SCNVector3(-1 * mat.m31, -1 * mat.m32, -1 * mat.m33) // orientation of camera in world space
let pos = SCNVector3(mat.m41, mat.m42, mat.m43) // location of camera in world space

return (dir, pos)
}
return (SCNVector3(0, 0, -1), SCNVector3(0, 0, -0.2))
}

SceneKit – Get direction of camera

You can create an SCNNode that place it in worldFront property to get a vector with the x, y, and z direction.

Another way you could do it is like how this project did it:

// Credit to https://github.com/farice/ARShooter

func getUserVector() -> (SCNVector3, SCNVector3) { // (direction, position)
if let frame = self.sceneView.session.currentFrame {
let mat = SCNMatrix4(frame.camera.transform) // 4x4 transform matrix describing camera in world space
let dir = SCNVector3(-1 * mat.m31, -1 * mat.m32, -1 * mat.m33) // orientation of camera in world space
let pos = SCNVector3(mat.m41, mat.m42, mat.m43) // location of camera in world space

return (dir, pos)
}
return (SCNVector3(0, 0, -1), SCNVector3(0, 0, -0.2))
}

How to get the SCNVector3 position of the camera in relation to it's direction ARKit Swift

Edited to better answer the question now that it's clarified in a comment

New Answer:

You are using only the position of the camera, so if the camera is rotated, it doesn't affect the ball.

What you can do is get the transform matrix of the ball and multiply it by the transform matrix of the camera, that way the ball position will be relative to the full transformation of the camera, including rotation.

e.g.

let ballShape = SCNSphere(radius: 0.03)
let ballNode = SCNNode(geometry: ballShape)
ballNode.position = SCNVector3Make(0.0, 0.0, -0.4)
let ballMatrix = ballNode.transform
let cameraMatrix = sceneView.pointOfView!.transform
let newBallMatrix = SCNMatrix4Mult(ballMatrix, cameraMatrix)
ballNode.transform = newBallMatrix
sceneView.scene.rootNode.addChildNode(ballNode)

Or if you only want the SCNVector3 position, to answer exactly to your question (this way the ball will not rotate):

...
let newBallMatrix = SCNMatrix4Mult(ballMatrix, cameraMatrix)
let newBallPosition = SCNVector3Make(newBallMatrix.m41, newBallMatrix.m42, newBallMatrix.m43)
ballNode.position = newBallPosition
sceneView.scene.rootNode.addChildNode(ballNode)

Old Answer:

You are using only the position of the camera, so when the camera rotates, it doesn't affect the ball.

SceneKit uses a hierarchy of nodes, so when a node is "child" of another node, it follows the position, rotation and scale of its "parent". The proper way of attaching an object to another object, in this case the camera, is to make it "child" of the camera.

Then, when you set the position, rotation or any other aspect of the transform of the "child" node, you are setting it relative to its parent. So if you set the position to SCNVector3Make(0.0, 0.0, -0.4), it's translated -0.4 units in Z on top of its "parent" translation.

So to make what you want, it should be:

let ballShape = SCNSphere(radius: 0.03)
let ballNode = SCNNode(geometry: ballShape)
ballNode.position = SCNVector3Make(0.0, 0.0, -0.4)
let cameraNode = sceneView.pointOfView
cameraNode?.addChildNode(ballNode)

This way, when the camera rotates, the ball follows exactly its rotation, but separated -0.4 units from the camera.

How to determine if an ScnNode is left or right of the view direction of a camera node in SceneKit

  1. Calculate the vector to the node relative to the camera. Calculate the vector by subtracting the camera position from the node position
  2. Use atan2 to convert the vector into an angle. The Apple docs are bit thin on explanation. There's a tutorial on raywenderlich.com.

E.g.

let v = CGPoint(x: nodePosition.x - cameraPosition.x, y: nodePosition.y - cameraPosition.y);
let a = atan2(v.y, v.x) // Note: order of arguments

The angle is in radians, and will range between -π and π (-180 to 180 degrees). If the value is negative the node is to the left of the camera vector, positive values mean the node is to the right, and zero means the node is straight ahead.

Conveniently, you can use this angle as-is to actually perform the rotation if you want. Just increment the camera angle by some fraction of the angle to the node.

E.g.

cameraRotation += a * 0.1

Caveats:

  • "Forward" or "Up" is not necessarily going to be the same as your internal coordinate system. Usually 0 is to the right, so if you want to show an arrow or other UI you may need to add or subtract an amount from the angle (usually -(π/2) to rotate a quarter turn).
  • The angle will always only be between -π and +π. If the node happens to fall directly behind the camera (exactly 180 degrees) you will get either -π or +π, and you might see the angle jump between these two extremes as the node cross over this discontinuity. You sometimes see the side effects of this in games where a nav arrow will point to a target then suddenly flick to the opposite side of the screen. There are various ways to handle this. One easy way is to use a low pass filter on the angle (ie accumulate multiple samples over time).

SceneKit: move camera towards direction its facing with pan gesture

In Objective C. The key part is the last three lines and specifically the order of multiplication of the matrices in the last line (causing the movement to happen in local space). If the transmat and cammat are switched it would behave again like you have now (moving in world space). The refactor part is just something that works for my specific situation where both perspective and orthographic camera is possible.

-(void)panCamera :(CGPoint)location {

CGFloat dx = _prevlocation.x - location.x;
CGFloat dy = location.y - _prevlocation.y;
_prevlocation = location;

//refactor dx and dy based on camera distance or orthoscale
if (cameraNode.camera.usesOrthographicProjection) {
dx = dx / 416 * cameraNode.camera.orthographicScale;
dy = dy / 416 * cameraNode.camera.orthographicScale;
} else {
dx = dx / 720 * cameraNode.position.z;
dy = dy / 720 * cameraNode.position.z;
}

SCNMatrix4 cammat = self.cameraNode.transform;
SCNMatrix4 transmat = SCNMatrix4MakeTranslation(dx, 0, dy);
self.cameraNode.transform = SCNMatrix4Mult(transmat, cammat);

}

Position a SceneKit object in front of SCNCamera's current orientation

(Swift 4)

Hey, you can use this simpler function if you want to put the object a certain position relative to another node (e.g. the camera node) and also in the same orientation as the reference node:

func updatePositionAndOrientationOf(_ node: SCNNode, withPosition position: SCNVector3, relativeTo referenceNode: SCNNode) {
let referenceNodeTransform = matrix_float4x4(referenceNode.transform)

// Setup a translation matrix with the desired position
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.x = position.x
translationMatrix.columns.3.y = position.y
translationMatrix.columns.3.z = position.z

// Combine the configured translation matrix with the referenceNode's transform to get the desired position AND orientation
let updatedTransform = matrix_multiply(referenceNodeTransform, translationMatrix)
node.transform = SCNMatrix4(updatedTransform)
}

If you'd like to put 'node' 2m right in front of a certain 'cameraNode', you'd call it like:

let position = SCNVector3(x: 0, y: 0, z: -2)
updatePositionAndOrientationOf(node, withPosition: position, relativeTo: cameraNode)

Edit: Getting the camera node

To get the camera node, it depends if you're using SceneKit, ARKit, or other framework. Below are examples for ARKit and SceneKit.

With ARKit, you have ARSCNView to render the 3D objects of an SCNScene overlapping the camera content. You can get the camera node from ARSCNView's pointOfView property:

let cameraNode = sceneView.pointOfView

For SceneKit, you have an SCNView that renders the 3D objects of an SCNScene. You can create camera nodes and position them wherever you want, so you'd do something like:

let scnScene = SCNScene()
// (Configure scnScene here if necessary)
scnView.scene = scnScene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 10) // For example
scnScene.rootNode.addChildNode(cameraNode)

Once a camera node has been setup, you can access the current camera in the same way as ARKit:

let cameraNode = scnView.pointOfView


Related Topics



Leave a reply



Submit