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,