如何在相机俯视的轴上旋转SCNNode? [英] How can I rotate an SCNNode on the axis the camera is looking down?

查看:474
本文介绍了如何在相机俯视的轴上旋转SCNNode?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我添加了 UIRotationGestureRecognizer ,并希望使用它来轮换用户选择的节点。



目前,它围绕z轴旋转,如下所示:

  private var startingRotation:CGFloat = 0 
@objc private func handleRotation(_ rotation:UIRotationGestureRecognizer){
guard let node = sceneView.hitTest(rotation.location(in:sceneView),options:nil).first?.node else {
return
}
如果rotation.state == .began {
startingRotation = CGFloat(node.rotation.w)
}
node.rotation = SCNVector4(0,0,1, -Float(startingRotation + rotation.rotation))
}

如果相机可以正常工作放置节点后没有移动。





但是,如果用户是mov在节点的一侧,它不再在相机所面对的轴上旋转。





如何围绕相机轴旋转?

解决方案

我想我理解你的问题,但你对Xartec答案的评论让我有点困惑,我是否真的这么做。



要重述:



目标是围绕通过绘制线条形成的向量旋转对象相机的原点是直通物体。这是垂直于相机平面的矢量,在这种情况下是手机屏幕。这个向量是摄像机的-Z轴。



解决方案



根据我对你的目标的理解,这里是什么你需要

  private var startingOrientation = GLKQuaternion.identity 
private var rotationAxis = GLKVector3Make(0,0,0)
@objc private func handleRotation(_ rotation:UIRotationGestureRecognizer){
guard let node = sceneView.hitTest(rotation.location(in:sceneView),options:nil).first?.node else {
返回
}
如果rotation.state == .began {
startingOrientation = GLKQuaternion(boxNode.orientation)
let cameraLookingDirection = sceneView.pointOfView!.parentFront
let cameraLookingDirectionInTargetNodesReference = boxNode.convertVector(cameraLookingDirection,
from:sceneView.pointOfView!.parent!)

rotationAxis = GLKVector3(cameraLookingDirectionIn) TargetNodesReference)
}否则如果rotation.state == .ended {
startingOrientation = GLKQuaternionIdentity
rotationAxis = GLKVector3Make(0,0,0)
}否则如果rotation.state == .changed {

//这将是应用于起始方向的总旋转数
let quaternion = GLKQuaternion(angle:Float(rotation.rotation),axis:rotationAxis)

//应用轮换
node.orientation = SCNQuaternion((startingOrientation * quaternion).normalized())
}
}



解释



真正关键的部分是找出你想要旋转的矢量,幸运的是,SceneKit提供了非常方便的方法。不幸的是,它们并没有提供你需要的所有方法。



首先,你需要代表摄影机前方的矢量(摄像机始终朝向它们的前轴)。 SCNNode.localFront 是-Z轴(0,0,-1),这只是SceneKit中的约定。但是你想要在摄像机的父坐标系中代表Z轴的轴。我发现我经常需要这个,我创建了一个扩展来从 SCNNode 获得 parentFront



现在我们有了相机的前轴

 让cameraLookingDirection = sceneView.pointOfView! .parentFront 

将其转换为目标的参考框架,我们使用



扩展程序



 扩展名SCNNode {

///父空间中的本地单位Y轴(0,1,0)。
var parentUp:SCNVector3 {

let transform = self.transform
返回SCNVector3(transform.m21,transform.m22,transform.m23)
}

///父空间中的本地单位X轴(1,0,0)。
var parentRight:SCNVector3 {

let transform = self.transform
返回SCNVector3(transform.m11,transform.m12,transform.m13)
}

///父空间中的本地单位-Z轴(0,0,-1)。
var parentFront:SCNVector3 {

let transform = self.transform
返回SCNVector3(-transform.m31,-transform.m32,-transform.m33)
}
}

扩展名GLKQuaternion {

init(向量:GLKVector3,标量:Float){

让glkVector = GLKVector3Make(向量。 x,vector.y,vector.z)

self = GLKQuaternionMakeWithVector3(glkVector,scalar)
}

init(angle:Float,axis:GLKVector3){

self = GLKQuaternionMakeWithAngleAndAxis(angle,axis.x,axis.y,axis.z)
}

func normalized() - > GLKQuaternion {

返回GLKQuaternionNormalize(self)
}

static var identity:GLKQuaternion {

返回GLKQuaternionIdentity
}
}

func *(左:GLKQuaternion,右:GLKQuaternion) - > GLKQuaternion {

返回GLKQuaternionMultiply(左,右)
}

扩展名SCNQuaternion {

init(_ quaternion:GLKQuaternion){

self = SCNVector4(quaternion.x,quaternion.y,quaternion.z,quaternion.w)
}
}

扩展名GLKQuaternion {

init(_ quaternion:SCNQuaternion){

self = GLKQuaternionMake(quaternion.x,quaternion.y,quaternion.z,quaternion.w)
}
}

扩展名GLKVector3 {

init(_ vector:SCNVector3){
self = SCNVector3ToGLKVector3(vector)
}
}


I've added a UIRotationGestureRecognizer and want to use it rotate a node that the user has selected.

Currently, it rotates around z-axis, like so:

private var startingRotation: CGFloat = 0
@objc private func handleRotation(_ rotation: UIRotationGestureRecognizer) {
    guard let node = sceneView.hitTest(rotation.location(in: sceneView), options: nil).first?.node else {
        return
    }
    if rotation.state == .began {
        startingRotation = CGFloat(node.rotation.w)
    }
    node.rotation = SCNVector4(0, 0, 1, -Float(startingRotation + rotation.rotation))
}

This works correctly if the camera hasn't moved since placing the node.

However, if the user moves to the side of the node, it no longer rotates on the axis the camera is facing.

How can I always rotate it around the camera's axis?

解决方案

I think I understand your question but your comment on Xartec's answer has me a little confused as to whether I really do.

To restate:

The goal is to rotate an object around the vector formed by drawing a line from the camera's origin "straight through" the object. Which is a vector perpendicular to the plane of the camera, in this case the phone screen. This vector is the camera's -Z axis.

Solution

Based on my understanding of your goal here is what you need

private var startingOrientation = GLKQuaternion.identity
private var rotationAxis = GLKVector3Make(0, 0, 0)
@objc private func handleRotation(_ rotation: UIRotationGestureRecognizer) {
    guard let node = sceneView.hitTest(rotation.location(in: sceneView), options: nil).first?.node else {
        return
    }
    if rotation.state == .began {
        startingOrientation = GLKQuaternion(boxNode.orientation)
        let cameraLookingDirection = sceneView.pointOfView!.parentFront
        let cameraLookingDirectionInTargetNodesReference = boxNode.convertVector(cameraLookingDirection,
                                                                                 from: sceneView.pointOfView!.parent!)

        rotationAxis = GLKVector3(cameraLookingDirectionInTargetNodesReference)
    } else if rotation.state == .ended {
        startingOrientation = GLKQuaternionIdentity
        rotationAxis = GLKVector3Make(0, 0, 0)
    } else if rotation.state == .changed {

        // This will be the total rotation to apply to the starting orientation
        let quaternion = GLKQuaternion(angle: Float(rotation.rotation), axis: rotationAxis)

        // Apply the rotation
        node.orientation = SCNQuaternion((startingOrientation * quaternion).normalized())
    }
}

Explanation

The really crucial part is figuring out which vector you want to rotate about, fortunately SceneKit provides methods that are very handy for doing that. Unfortunately, they do not provide all the methods you need.

First, you need the vector that represents the camera's front (camera's are always looking toward their front axis). SCNNode.localFront is the -Z axis (0, 0, -1), this is simply a convention in SceneKit. But you want the axis that represents the Z axis in the camera's parent's coordinate system. I find that I need this so often, that I created an extension to get parentFront from an SCNNode.

Now we have the camera's front axis

let cameraLookingDirection = sceneView.pointOfView!.parentFront

to convert it to the target's reference frame, we use convertVector(_,from:) to get a vector that we can apply a rotation with. The result of this method will be the -Z axis of the box when the scene is first launched (like in your static code, but you used the Z axis and negated the angle).

let cameraLookingDirectionInTargetNodesReference = boxNode.convertVector(cameraLookingDirection, from: sceneView.pointOfView!.parent!)

To achieve an additive rotation, which is the part I'm not clear whether you need, I used quaternions instead of vector rotations. Basically, I take the orientation of the box when the gesture starts and apply a rotation via quaternion multiplication. These 2 lines:

let quaternion = GLKQuaternion(angle: Float(rotation.rotation), axis: rotationAxis)
node.orientation = SCNQuaternion((startingOrientation * quaternion).normalized())

This math can also be done with rotation vectors or transformation matrices, but this is the method that I'm familiar with.

Result

Extensions

extension SCNNode {

    /// The local unit Y axis (0, 1, 0) in parent space.
    var parentUp: SCNVector3 {

        let transform = self.transform
        return SCNVector3(transform.m21, transform.m22, transform.m23)
    }

    /// The local unit X axis (1, 0, 0) in parent space.
    var parentRight: SCNVector3 {

        let transform = self.transform
        return SCNVector3(transform.m11, transform.m12, transform.m13)
    }

    /// The local unit -Z axis (0, 0, -1) in parent space.
    var parentFront: SCNVector3 {

        let transform = self.transform
        return SCNVector3(-transform.m31, -transform.m32, -transform.m33)
    }
}

extension GLKQuaternion {

    init(vector: GLKVector3, scalar: Float) {

        let glkVector = GLKVector3Make(vector.x, vector.y, vector.z)

        self = GLKQuaternionMakeWithVector3(glkVector, scalar)
    }

    init(angle: Float, axis: GLKVector3) {

        self = GLKQuaternionMakeWithAngleAndAxis(angle, axis.x, axis.y, axis.z)
    }

    func normalized() -> GLKQuaternion {

        return GLKQuaternionNormalize(self)
    }

    static var identity: GLKQuaternion {

        return GLKQuaternionIdentity
    }
}

func * (left: GLKQuaternion, right: GLKQuaternion) -> GLKQuaternion {

    return GLKQuaternionMultiply(left, right)
}

extension SCNQuaternion {

    init(_ quaternion: GLKQuaternion) {

        self = SCNVector4(quaternion.x, quaternion.y, quaternion.z, quaternion.w)
    }
}

extension GLKQuaternion {

    init(_ quaternion: SCNQuaternion) {

        self = GLKQuaternionMake(quaternion.x, quaternion.y, quaternion.z, quaternion.w)
    }
}

extension GLKVector3 {

    init(_ vector: SCNVector3) {
        self = SCNVector3ToGLKVector3(vector)
    }
}

这篇关于如何在相机俯视的轴上旋转SCNNode?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆