How can I rotate an SCNNode on the axis the camera is looking down?

后端 未结 2 943
深忆病人
深忆病人 2020-12-30 07:15

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:

2条回答
  •  别那么骄傲
    2020-12-30 08:14

    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)
        }
    }
    

提交回复
热议问题