I'm currently working on a game in scenekit with swift, and i've got a spaceship flying around. I'm using the following code to make the camera follow the spaceship:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
updateCameraPosition()
}
func updateCameraPosition () {
let currentPosition = player.node.presentation.position
let x: Float = lerp(a: Float(prevCameraPosition.x), b: currentPosition.x, t: 0.03)
let z: Float = lerp(a: Float(prevCameraPosition.z), b: currentPosition.z, t: 0.03) + (cameraZoom/2)
let vector = SCNVector3(x: x, y: cameraZoom, z: z)
cameraNode.runAction(SCNAction.move(to: vector, duration: 0.2))
prevCameraPosition = currentPosition
}
func lerp (a: Float, b: Float, t: Float) -> Float {
return (1 - t) * a + t * b;
}
in addition to just following the ship, it add's some nice offset motion when you change directions for a nice fluid camera.
The problem i'm facing is the ship glitches back and forth a good portion of the time, it always moves in the correct directions, but it almost looks like the ship position is getting reset back a few frames. You can see this in action with this video.
Without the camera follow code, the ship moves much smoother, as you can see in this video
Can anybody see anything wrong with my code that is maybe inefficient? Maybe there is a more optimized way to do this? Any tips/resources/advice is greatly appriciated!
You need to stop the previous SCNAction before applying new SCNAction.
To add an action with a key you use the
cameraNode.runAction(SCNAction, forKey: String)
method.
Now you can remove a specific action by that key
cameraNode.removeAction(forKey: String)
Related
I am completely new to IOS development and Swift. At present I am working on an IOS app that involves scanning a room using LiDAR sensor of IPad and later when I load the 3D Obj file and touch two arbitrary points, the length between two points should be displayed. Something similar to 3D scanner App, Canvas app.
So far I am able to export the mesh data in to an Obj file and save it to the device. I have tried for a while, but I think am kind of stuck at this point as I do not know how to proceed further for the measuring part.
The end result should look something like this.
an exported obj file with the distance label
Looking for any guidance/suggestions.
Scenekit uses meters, just fyi. You may have to experiment with scaling on this, I kind of doubt it will match out of the box. This assumes you have the nodes to compare distances, otherwise it's a different deal.
You can use GLKVector3Distance, or just roll your own:
func distance3D(vector1: SCNVector3, vector2: SCNVector3) -> Float
{
let x: Float = (vector1.x - vector2.x) * (vector1.x - vector2.x)
let y: Float = (vector1.y - vector2.y) * (vector1.y - vector2.y)
let z: Float = (vector1.z - vector2.z) * (vector1.z - vector2.z)
let temp = x + y + z
return Float(sqrtf(Float(temp)))
}
or:
extension SCNVector3 {
func distance(to vector: SCNVector3) -> Float {
return simd_distance(simd_float3(self), simd_float3(vector))
}
}
I have an issue with ARkit, I use ARWorldTrackingConfiguration and when I have a SCNNode one meter away from me, when I use Iphone X and move to the SCNNode, the Z position of my camera SCNNode updates and I know that I am closer to the node, but with other Iphones (Iphone 8) I don't get an update to Z position.
Also with GPS even with kCLLocationAccuracyBestForNavigation I don't have accuracy.
How can I know that I am closer to the SCNNode?? Thank you
implementing ARSCNViewDelegate in your ViewController, it will be called the renderer:updateAtTime callback once per frame.
Inside this function you can get the position of your camera relative to the real world calling sceneView.session.currentFrame.camera.transform which is a simd_float4x4. You can access the current position of your camera with transform.columns.3 and peeking the x,y,z field from it. With these coordinates you can calculate the distance, such the Euclidean distance with these functions
func distanceTravelled(xDist:Float, yDist:Float, zDist:Float) -> Float{
return sqrt((xDist*xDist)+(yDist*yDist)+(zDist*zDist))
}
func distanceTravelled(between v1:SCNVector3,and v2:SCNVector3) -> Float{
let xDist = v1.x - v2.x
let yDist = v1.y - v2.y
let zDist = v1.z - v2.z
return distanceTravelled(xDist: xDist, yDist: yDist, zDist: zDist)
}
Remember to convert the coordinates of the nodes to worldCoordinates -> node.worldPosition with node an instance of SCNNode
I am using two virtual joysticks to move my camera around the scene. The left stick controls the position and the right one controls the rotation.
When using the right stick, the camera rotates, but it seems that the camera rotates around the center point of the model.
This is my code:
fileprivate func rotateCamera(_ x: Float, _ y: Float)
{
if let cameraNode = self.cameraNode
{
let moveX = x / 50.0
let rotated = SCNMatrix4Rotate(cameraNode.transform, moveX, 0, 1, 0)
cameraNode.transform = rotated
}
}
I have also tried this code:
fileprivate func rotateCamera(_ x: Float, _ y: Float)
{
if let cameraNode = self.cameraNode
{
let moveX = x / 50.0
cameraNode.rotate(by: SCNQuaternion(moveX, 0, 1, 0), aroundTarget: cameraNode.transform)
}
}
But the camera just jumps around. What is my error here?
There are many ways to handle rotation, some are very suitable for giving headaches to the coder.
It sounds like the model is at 0,0,0, meaning it’s in the center of the world, and the camera is tranformed to a certain location. In the first example using matrices, you basically rotate that transformation. So you transform first, then rotate, which yes will cause it to rotate around the origin (0,0,0).
What you should do instead, to rotate the camera in local space, is rotate the camera first in local space and then translate it to its position in world space.
Translation x rotation matrix results in rotation in world space
Rotation x translation matrix results in rotation in local space
So a solution is to remove the translation from the camera first (moving it back to 0,0,0), then apply the rotation matrix, and then reapply the translation. This comes down to the same result as starting with an identity matrix. For example:
let rotated = SCNMatrix4Rotate(SCNMatrixIdentity, moveX, 0, 1, 0)
cameraNode.transform = SCNMatrix4Multiply(rotated, cameraNode.transform)
Hello everyone,
I come back to you about my current problem. I already asked a question about that but no one had success to help me. Then I will explain my complete problem and how I tried to fix it. (I tried several things)
So, I need to code a lib that adds many functions in order to manage cameras and objects in a 3D world. For that we have chosen SceneKit Framework to use Metal.
I will post a very simplified code but all necessary things are here.
To illustrate my thought here is a GIF which explains how I want my camera acts like:
http://i.stack.imgur.com/5tHUl.gif
This comes from Stack question (thanks rickster) : Rotate SCNCamera node looking at an object around an imaginary sphere
The goal is to load a scene and handle User Pan Action to move camera around the gravity center point of a 3D object in my 3D world. Here is my basic simplified code:
import SceneKit
import UIKit
class SceneManager
{
private let scene: SCNScene
private let view: SCNView
private let camera: SCNNode
private let cameraOrbit: SCNNode
init(view: SCNView, assetFolder: String, sceneName: String, cameraName: String, backgroundColor: UIColor) {
self.view = view
self.scene = SCNScene(named: (assetFolder + "/" + sceneName))!
if (self.scene.rootNode.childNodeWithName(cameraName, recursively: true) == nil) {
print("Fatal error: Cannot find camera in scene with name :\"", cameraName, "\"")
exit(1)
}
self.camera = self.scene.rootNode.childNodeWithName(cameraName, recursively: true)! // Retrieve cameraNode created in scene file
self.camera.removeFromParentNode()
self.cameraOrbit = SCNNode()
self.cameraOrbit.addChildNode(self.camera)
self.scene.rootNode.addChildNode(self.cameraOrbit)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panHandler(_:)))
panGesture.maximumNumberOfTouches = 1
self.view.addGestureRecognizer(panGesture)
self.view.backgroundColor = backgroundColor
self.view.pointOfView = cameraNode
self.view.scene = self.scene
}
#objc private func panHandler(sender: UIPanGestureRecognizer) {
// some code here
}
}
Then I have explore many possible solutions I had found on Internet. I will present the principal solution I had explore.
1) EulerAngle
Src: Rotate SCNCamera node looking at an object around an imaginary sphere
I wanted to applied the rickster's method in this Stack. Here is my trying code:
#objc private func panHandler(sender: UIPanGestureRecognizer) {
if (sender.state == UIGestureRecognizerState.Changed) {
let scrollWidthRatio = Float(sender.velocityInView(sender.view!).x) / 10000 * -1
let scrollHeightRatio = Float(sender.velocityInView(sender.view!).y) / 10000
cameraOrbit.eulerAngles.y += Float(-2 * M_PI) * scrollWidthRatio
cameraOrbit.eulerAngles.x += Float(-M_PI) * scrollHeightRatio
}
}
That works well for Y axis but the camera spin around itself on X axis. I do not understand very well what refers to the eulerAngle but I notice the Z axis never changes. Is this why the rotation is not spherical?
2) Homemade solution
src: SceneKit Child node position not changed during Parent node rotation
That is a question I have posted about the worldPosition and localPosition. But the solution proposed did not work... (Or I did not understand)
This is the solution I will use principally. But if you have another solution, I am ready to explore and try it!
Theoretically the var alpha is the angle between abscissa axis and position of the camera (2D (x, z)) in trigonometry circle. I use that to calculate the ratio to apply at X and Z rotation's axes.
The goal is to have a rotation that follows a segment on 2D plan (x, z).
But that did not work cause of self.camera.position is coordinated relative to cameraOrbit (parent node) and it needs the worldPosition property.
The parameters of camera worldTransform is a matrix I did not understand so I can not use it. Even if I can use the worldPosition property, I am not sure that it will work very well cause of my angle and ratio applied to X and Z axes.
What do you think about this, maybe I need to change my method?
#objc private func panHandler(sender: UIPanGestureRecognizer) {
let cameraROrbitRadius = sqrt(pow(self.camera.position.x, 2) + pow(self.camera.position.y, 2))
let alpha = cos(self.camera.position.z / self.cameraOrbitRadius) // Get angle of camera
var ratioX = 1 - ((CGFloat)(alpha) / (CGFloat)(M_PI)) // Get the ratio with angle for apply to Z and X axes rotation
var ratioZ = ((CGFloat)(alpha) / (CGFloat)(M_PI))
// Change direction of rotation depending camera's position in trigonometric circle
if (self.camera.position.x > 0 && self.camera.position.z < 0) {
ratioX *= -1
} else if (self.camera.position.z < 0 && self.camera.position.x < 0) {
ratioX *= -1
ratioZ *= -1
} else if (self.camera.position.z > 0 && self.camera.position.x > 0) {
ratioZ *= -1
}
// Set the angle rotation to add at imaginary sphere (cameraOrbit)
let xAngleToAdd = (sender.velocityInView(sender.view!).y / 10000) * ratioX
let yAngleToAdd = (sender.velocityInView(sender.view!).x / 10000) * (-1)
let zAngleToAdd = (sender.velocityInView(sender.view!).y / 10000) * ratioZ
let rotation = SCNAction.rotateByX(xAngleToAdd, y: yAngleToAdd, z: zAngleToAdd, duration: 0.5)
self.cameraOrbit.runAction(rotation)
}
This method works for Y axis too. But the rotation above the object works bad. I can not explain it simply but the rotation of camera is offbeat of this theoretically movement.
I think I have explain every important things. If you have any ideas or tips?
Regards,
I have a scene with multiple nodes. I want to select a node by tapping it (if I tap nothing I want nothing to happen) and make him follow my finger only on XY axis (I know position on Z axis). Is there any method that converts location in view to SceneKit coords?
After few researches I found this and it's exactly what I want, but I don't get the code. Can somebody explain me or help me figure how can I solve my problem?
https://www.youtube.com/watch?v=xn9Bt2PFp0g
func CGPointToSCNVector3(view: SCNView, depth: Float, point: CGPoint) -> SCNVector3 {
let projectedOrigin = view.projectPoint(SCNVector3Make(0, 0, depth))
let locationWithz = SCNVector3Make(Float(point.x), Float(point.y), projectedOrigin.z)
return view.unprojectPoint(locationWithz)
}
Looks like was pretty simple, I've made a function that gets 3 parameters. View is the SCNView where scene is attached to, depth is the z value of node, and point is a CGPoint that represents projection of 3D scene on screen.
This is Alec Firtulescu's answer as an extension for watchOS (convert it to iOS by changing WKInterfaceSCNScene to SCNView):
extension CGPoint {
func scnVector3Value(view: WKInterfaceSCNScene, depth: Float) -> SCNVector3 {
let projectedOrigin = view.projectPoint(SCNVector3(0, 0, depth))
return view.unprojectPoint(SCNVector3(Float(x), Float(y), projectedOrigin.z))
}
}