如何使用平移手势在 SceneKit 中使用四元数旋转相机
How to use a pan gesture to rotate a camera in SceneKit using quaternions
我正在使用 iOS SceneKit 框架构建 360 度视频查看器。
我想使用 UIPanGestureRecognizer 来控制相机的方向。
SCNNodes 有几个属性我们可以用来指定它们的旋转:rotation
(一个旋转矩阵),orientation
(一个四元数),eulerAngles
(每个轴角)。
我读过的所有内容都说要避免使用欧拉角以避免 gimbal lock。
我想使用四元数有几个原因,我不会在这里详述。
我无法让它正常工作。相机控制几乎是我想要的地方,但有些地方不对劲。尽管我试图只影响 X 轴和 Y 轴,但看起来相机正在绕 Z 轴旋转。
我认为这个问题与我的四元数乘法逻辑有关。多年来我没有做过任何与四元数相关的事情:(我的平移手势处理程序在这里:
func didPan(recognizer: UIPanGestureRecognizer)
{
switch recognizer.state
{
case .Began:
self.previousPanTranslation = .zero
case .Changed:
guard let previous = self.previousPanTranslation else
{
assertionFailure("Attempt to unwrap previous pan translation failed.")
return
}
// Calculate how much translation occurred between this step and the previous step
let translation = recognizer.translationInView(recognizer.view)
let translationDelta = CGPoint(x: translation.x - previous.x, y: translation.y - previous.y)
// Use the pan translation along the x axis to adjust the camera's rotation about the y axis.
let yScalar = Float(translationDelta.x / self.view.bounds.size.width)
let yRadians = yScalar * self.dynamicType.MaxPanGestureRotation
// Use the pan translation along the y axis to adjust the camera's rotation about the x axis.
let xScalar = Float(translationDelta.y / self.view.bounds.size.height)
let xRadians = xScalar * self.dynamicType.MaxPanGestureRotation
// Use the radian values to construct quaternions
let x = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0)
let y = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0)
let z = GLKQuaternionMakeWithAngleAndAxis(0, 0, 0, 1)
let combination = GLKQuaternionMultiply(z, GLKQuaternionMultiply(y, x))
// Multiply the quaternions to obtain an updated orientation
let scnOrientation = self.cameraNode.orientation
let glkOrientation = GLKQuaternionMake(scnOrientation.x, scnOrientation.y, scnOrientation.z, scnOrientation.w)
let q = GLKQuaternionMultiply(combination, glkOrientation)
// And finally set the current orientation to the updated orientation
self.cameraNode.orientation = SCNQuaternion(x: q.x, y: q.y, z: q.z, w: q.w)
self.previousPanTranslation = translation
case .Ended, .Cancelled, .Failed:
self.previousPanTranslation = nil
case .Possible:
break
}
}
我的代码在这里开源:https://github.com/alfiehanssen/360Player/
特别检查 pan-gesture
分支:
https://github.com/alfiehanssen/360Player/tree/pan-gesture
如果您拉下代码,我相信您必须 运行 在设备上而不是在模拟器上。
我在这里发布了一个演示错误的视频(请原谅视频文件的低分辨率):
https://vimeo.com/174346191
在此先感谢您的帮助!
抱歉,这使用 SCNVector4
而不是四元数,但对我来说效果很好。我将它应用到我最根的几何节点容器 ("rotContainer") 而不是相机,但一个简短的测试似乎表明它也适用于相机使用。
func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
let pan_x = Float(translation.x)
let pan_y = Float(-translation.y)
let anglePan = sqrt(pow(pan_x,2)+pow(pan_y,2))*(Float)(M_PI)/180.0
var rotationVector = SCNVector4()
rotationVector.x = -pan_y
rotationVector.y = pan_x
rotationVector.z = 0
rotationVector.w = anglePan
rotContainer.rotation = rotationVector
if(sender.state == UIGestureRecognizerState.Ended) {
let currentPivot = rotContainer.pivot
let changePivot = SCNMatrix4Invert(rotContainer.transform)
rotContainer.pivot = SCNMatrix4Mult(changePivot, currentPivot)
rotContainer.transform = SCNMatrix4Identity
}
}
我能够使用四元数来完成这项工作。完整代码在这里:ThreeSixtyPlayer。样本在这里:
let orientation = cameraNode.orientation
// Use the pan translation along the x axis to adjust the camera's rotation about the y axis (side to side navigation).
let yScalar = Float(translationDelta.x / translationBounds.size.width)
let yRadians = yScalar * maxRotation
// Use the pan translation along the y axis to adjust the camera's rotation about the x axis (up and down navigation).
let xScalar = Float(translationDelta.y / translationBounds.size.height)
let xRadians = xScalar * maxRotation
// Represent the orientation as a GLKQuaternion
var glQuaternion = GLKQuaternionMake(orientation.x, orientation.y, orientation.z, orientation.w)
// Perform up and down rotations around *CAMERA* X axis (note the order of multiplication)
let xMultiplier = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0)
glQuaternion = GLKQuaternionMultiply(glQuaternion, xMultiplier)
// Perform side to side rotations around *WORLD* Y axis (note the order of multiplication, different from above)
let yMultiplier = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0)
glQuaternion = GLKQuaternionMultiply(yMultiplier, glQuaternion)
cameraNode.orientation = SCNQuaternion(x: glQuaternion.x, y: glQuaternion.y, z: glQuaternion.z, w: glQuaternion.w)
bbedit 的解决方案与 Rotating a camera on an orbit 结合得很好。按照链接答案中的建议设置相机,然后使用 bbedit 的想法旋转 "orbit" 节点。我修改了 Swift 4 版本的代码,他的代码确实有效:
@IBAction func handlePan(_ sender: UIPanGestureRecognizer) {
print("Called the handlePan method")
let scnView = self.view as! SCNView
let cameraOrbit = scnView.scene?.rootNode.childNode(withName: "cameraOrbit", recursively: true)
let translation = sender.translation(in: sender.view!)
let pan_x = Float(translation.x)
let pan_y = Float(-translation.y)
let anglePan = sqrt(pow(pan_x,2)+pow(pan_y,2))*(Float)(Double.pi)/180.0
var rotationVector = SCNVector4()
rotationVector.x = -pan_y
rotationVector.y = pan_x
rotationVector.z = 0
rotationVector.w = anglePan
cameraOrbit!.rotation = rotationVector
if(sender.state == UIGestureRecognizerState.ended) {
let currentPivot = cameraOrbit!.pivot
let changePivot = SCNMatrix4Invert(cameraOrbit!.transform)
cameraOrbit!.pivot = SCNMatrix4Mult(changePivot, currentPivot)
cameraOrbit!.transform = SCNMatrix4Identity
}
}
我正在使用 iOS SceneKit 框架构建 360 度视频查看器。
我想使用 UIPanGestureRecognizer 来控制相机的方向。
SCNNodes 有几个属性我们可以用来指定它们的旋转:rotation
(一个旋转矩阵),orientation
(一个四元数),eulerAngles
(每个轴角)。
我读过的所有内容都说要避免使用欧拉角以避免 gimbal lock。
我想使用四元数有几个原因,我不会在这里详述。
我无法让它正常工作。相机控制几乎是我想要的地方,但有些地方不对劲。尽管我试图只影响 X 轴和 Y 轴,但看起来相机正在绕 Z 轴旋转。
我认为这个问题与我的四元数乘法逻辑有关。多年来我没有做过任何与四元数相关的事情:(我的平移手势处理程序在这里:
func didPan(recognizer: UIPanGestureRecognizer)
{
switch recognizer.state
{
case .Began:
self.previousPanTranslation = .zero
case .Changed:
guard let previous = self.previousPanTranslation else
{
assertionFailure("Attempt to unwrap previous pan translation failed.")
return
}
// Calculate how much translation occurred between this step and the previous step
let translation = recognizer.translationInView(recognizer.view)
let translationDelta = CGPoint(x: translation.x - previous.x, y: translation.y - previous.y)
// Use the pan translation along the x axis to adjust the camera's rotation about the y axis.
let yScalar = Float(translationDelta.x / self.view.bounds.size.width)
let yRadians = yScalar * self.dynamicType.MaxPanGestureRotation
// Use the pan translation along the y axis to adjust the camera's rotation about the x axis.
let xScalar = Float(translationDelta.y / self.view.bounds.size.height)
let xRadians = xScalar * self.dynamicType.MaxPanGestureRotation
// Use the radian values to construct quaternions
let x = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0)
let y = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0)
let z = GLKQuaternionMakeWithAngleAndAxis(0, 0, 0, 1)
let combination = GLKQuaternionMultiply(z, GLKQuaternionMultiply(y, x))
// Multiply the quaternions to obtain an updated orientation
let scnOrientation = self.cameraNode.orientation
let glkOrientation = GLKQuaternionMake(scnOrientation.x, scnOrientation.y, scnOrientation.z, scnOrientation.w)
let q = GLKQuaternionMultiply(combination, glkOrientation)
// And finally set the current orientation to the updated orientation
self.cameraNode.orientation = SCNQuaternion(x: q.x, y: q.y, z: q.z, w: q.w)
self.previousPanTranslation = translation
case .Ended, .Cancelled, .Failed:
self.previousPanTranslation = nil
case .Possible:
break
}
}
我的代码在这里开源:https://github.com/alfiehanssen/360Player/
特别检查 pan-gesture
分支:
https://github.com/alfiehanssen/360Player/tree/pan-gesture
如果您拉下代码,我相信您必须 运行 在设备上而不是在模拟器上。
我在这里发布了一个演示错误的视频(请原谅视频文件的低分辨率): https://vimeo.com/174346191
在此先感谢您的帮助!
抱歉,这使用 SCNVector4
而不是四元数,但对我来说效果很好。我将它应用到我最根的几何节点容器 ("rotContainer") 而不是相机,但一个简短的测试似乎表明它也适用于相机使用。
func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
let pan_x = Float(translation.x)
let pan_y = Float(-translation.y)
let anglePan = sqrt(pow(pan_x,2)+pow(pan_y,2))*(Float)(M_PI)/180.0
var rotationVector = SCNVector4()
rotationVector.x = -pan_y
rotationVector.y = pan_x
rotationVector.z = 0
rotationVector.w = anglePan
rotContainer.rotation = rotationVector
if(sender.state == UIGestureRecognizerState.Ended) {
let currentPivot = rotContainer.pivot
let changePivot = SCNMatrix4Invert(rotContainer.transform)
rotContainer.pivot = SCNMatrix4Mult(changePivot, currentPivot)
rotContainer.transform = SCNMatrix4Identity
}
}
我能够使用四元数来完成这项工作。完整代码在这里:ThreeSixtyPlayer。样本在这里:
let orientation = cameraNode.orientation
// Use the pan translation along the x axis to adjust the camera's rotation about the y axis (side to side navigation).
let yScalar = Float(translationDelta.x / translationBounds.size.width)
let yRadians = yScalar * maxRotation
// Use the pan translation along the y axis to adjust the camera's rotation about the x axis (up and down navigation).
let xScalar = Float(translationDelta.y / translationBounds.size.height)
let xRadians = xScalar * maxRotation
// Represent the orientation as a GLKQuaternion
var glQuaternion = GLKQuaternionMake(orientation.x, orientation.y, orientation.z, orientation.w)
// Perform up and down rotations around *CAMERA* X axis (note the order of multiplication)
let xMultiplier = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0)
glQuaternion = GLKQuaternionMultiply(glQuaternion, xMultiplier)
// Perform side to side rotations around *WORLD* Y axis (note the order of multiplication, different from above)
let yMultiplier = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0)
glQuaternion = GLKQuaternionMultiply(yMultiplier, glQuaternion)
cameraNode.orientation = SCNQuaternion(x: glQuaternion.x, y: glQuaternion.y, z: glQuaternion.z, w: glQuaternion.w)
bbedit 的解决方案与 Rotating a camera on an orbit 结合得很好。按照链接答案中的建议设置相机,然后使用 bbedit 的想法旋转 "orbit" 节点。我修改了 Swift 4 版本的代码,他的代码确实有效:
@IBAction func handlePan(_ sender: UIPanGestureRecognizer) { print("Called the handlePan method") let scnView = self.view as! SCNView let cameraOrbit = scnView.scene?.rootNode.childNode(withName: "cameraOrbit", recursively: true) let translation = sender.translation(in: sender.view!) let pan_x = Float(translation.x) let pan_y = Float(-translation.y) let anglePan = sqrt(pow(pan_x,2)+pow(pan_y,2))*(Float)(Double.pi)/180.0 var rotationVector = SCNVector4() rotationVector.x = -pan_y rotationVector.y = pan_x rotationVector.z = 0 rotationVector.w = anglePan cameraOrbit!.rotation = rotationVector if(sender.state == UIGestureRecognizerState.ended) { let currentPivot = cameraOrbit!.pivot let changePivot = SCNMatrix4Invert(cameraOrbit!.transform) cameraOrbit!.pivot = SCNMatrix4Mult(changePivot, currentPivot) cameraOrbit!.transform = SCNMatrix4Identity } }