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

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

问题描述

我添加了一个 UIRotationGestureRecognizer 并希望使用它来旋转用户选择的节点.

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

私有变量startingRotation:CGFloat = 0@objc private func handleRotation(_ rotation: UIRotationGestureRecognizer) {守卫让节点 = sceneView.hitTest(rotation.location(in:sceneView), options: nil).first?.node else {返回}如果旋转.状态 == .began {开始旋转 = CGFloat(node.rotation.w)}node.rotation = SCNVector4(0, 0, 1, -Float(startingRotation + rotation.rotation))}

如果自放置节点后相机没有移动,这将正常工作.

然而,如果用户移动到节点的一侧,它不再围绕相机所面对的轴旋转.

如何始终围绕相机的轴旋转它?

解决方案

我想我理解你的问题,但你对 Xartec 回答的评论让我有点困惑我是否真的理解.

重申:

目标是围绕通过从相机的原点直接穿过"对象绘制一条线而形成的矢量旋转对象.这是一个垂直于相机平面的向量,在这种情况下是手机屏幕.这个向量是相机的 -Z 轴.

解决方案

根据我对您目标的理解,这就是您所需要的

私有变量startingOrientation = GLKQuaternion.identity私有变量旋转轴 = GLKVector3Make(0, 0, 0)@objc private func handleRotation(_ rotation: UIRotationGestureRecognizer) {守卫让节点 = sceneView.hitTest(rotation.location(in:sceneView), options: nil).first?.node else {返回}如果旋转.状态 == .began {起始方向 = GLKQuaternion(boxNode.orientation)让 cameraLookingDirection = sceneView.pointOfView!.parentFront让 cameraLookingDirectionInTargetNodesReference = boxNode.convertVector(cameraLookingDirection,来自:sceneView.pointOfView!.parent!)旋转轴 = GLKVector3(cameraLookingDirectionInTargetNodesReference)} else if rotation.state == .ended {起始方向 = GLKQuaternionIdentity旋转轴 = GLKVector3Make(0, 0, 0)} else if rotation.state == .changed {//这将是应用于起始方向的总旋转量让四元数 = 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

为了将其转换为目标的参考系,我们使用

扩展

extension SCNNode {///父空间中的局部单位 Y 轴 (0, 1, 0).var parentUp: SCNVector3 {让变换 = self.transform返回 SCNVector3(transform.m21,transform.m22,transform.m23)}///父空间中的局部单位 X 轴 (1, 0, 0).var parentRight: SCNVector3 {让变换 = self.transform返回 SCNVector3(transform.m11,transform.m12,transform.m13)}///父空间中的局部单位-Z轴(0, 0, -1).var parentFront: SCNVector3 {让变换 = self.transform返回 SCNVector3(-transform.m31, -transform.m32, -transform.m33)}}扩展 GLKQuaternion {初始化(向量:GLKVector3,标量:浮点数){让 glkVector = GLKVector3Make(vector.x, vector.y, vector.z)self = GLKQuaternionMakeWithVector3(glkVector, 标量)}初始化(角度:浮动,轴:GLKVector3){self = GLKQuaternionMakeWithAngleAndAxis(angle,axis.x,axis.y,axis.z)}func normalized() ->GLK四元数{返回 GLKQuaternionNormalize(self)}静态变量身份:GLKQuaternion {返回 GLKQuaternionIdentity}}func *(左:GLKQuaternion,右:GLKQuaternion)->GLK四元数{返回 GLKQuaternionMultiply(左,右)}扩展 SCNQuaternion {初始化(_四元数:GLKQuaternion){self = SCNVector4(quaternion.x, quaternion.y, quaternion.z, quaternion.w)}}扩展 GLKQuaternion {初始化(_四元数:SCNQuaternion){self = GLKQuaternionMake(quaternion.x, quaternion.y, quaternion.z, quaternion.w)}}扩展 GLKVector3 {初始化(_ 向量: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天全站免登陆