首页 文章

SceneKit - 根据方向旋转带有平移手势的 SCNNode

提问于
浏览
3

我正在编写我的第一个场景包项目,我正在尝试使用平移手势来旋转场景中的一个简单对象(对象是从.dae 文件导入的简单 L 立方体形状,正确设置了轴心点)。

我经历了多个 SO 解决方案和教程,并且我已经整理了一些代码,但轮换工作不正常。如果我反复尝试沿一个轴旋转对象,它可以正常工作,但是当我尝试另一个方向时,在平移开始时,对象重置为它的初始位置。有时旋转也会随机怪癖或跳跃。我不确定我是否使用了正确的方法,请指教..这是我的代码:

func handlePan(sender: UIPanGestureRecognizer){

    // determine pan direction
    let velocity: CGPoint = sender.velocity(in: sender.view!)
    if self.panDirection == nil {
        if velocity.x > 0 && velocity.x > abs(velocity.y) { self.panDirection = "right" }
        if velocity.x < 0 && abs(velocity.x) > abs(velocity.y) { self.panDirection = "left" }
        if velocity.y < 0 && abs(velocity.y) > abs(velocity.x) { self.panDirection = "up" }
        if velocity.y > 0 &&  velocity.y  > abs(velocity.x) { self.panDirection = "down" }
    }

    // do rotation only on selected SCNNode
    if self.selectedBrickNode != nil {

        // start of pan gesture
        if sender.state == UIGestureRecognizerState.began{
            // remember initial rotation angle
            self.initRot = self.selectedBrickNode.rotation.w
        }

        let translation = sender.translation(in: sender.view!)
        let pan_x =  Float(translation.x)
        let pan_y =  Float(-translation.y)

        // add rotation angle to initial rotation
        var anglePan = self.initRot + (Float)(sqrt(pow(pan_x,2)+pow(pan_y,2)))*(Float)(Double.pi)/180.0
        var rotVector = SCNVector4()

        // if left/right, rotate on Y axis
        rotVector.x = (self.panDirection == "left" || self.panDirection == "right" ) ? 0 : -pan_y
        // if up/down, rotate on X axis
        rotVector.y = (self.panDirection == "up" || self.panDirection == "down" ) ? 0 : pan_x
        rotVector.z = 0
        rotVector.w = anglePan

        // set SCNNode's rotation
        self.selectedBrickNode.rotation = rotVector

        // end of pan gesture
        if(sender.state == UIGestureRecognizerState.ended) {

            // reset initial rotation 
            self.initRot = 0.0   

            // calculate degrees so we can snap to 90deg increments
            var angle = anglePan * (Float) (180.0 /  Double.pi)

            // snap to 90deg increments
            let diff = angle.truncatingRemainder(dividingBy: 90.0)
            if diff <= 45 {
                angle = angle - diff
            }else{
                angle = (angle - diff ) + 90
            }

            // set new rotation to snap
            rotVector.w = angle * (Float)(Double.pi)/180.0
            self.selectedBrickNode.rotation = rotVector
            self.selectedBrickNode = nil
        }
    }

}

1 回答

  • 5

    经过大量的研究和几个小时的墙,我最终找到了一个有效的解决方案,希望它可以帮助有类似目标的人。

    我的工作代码:

    func handlePan(sender: UIPanGestureRecognizer){
    
        // get pan direction
        let velocity: CGPoint = sender.velocity(in: sender.view!)
        if self.panDirection == nil {
            self.panDirection = GameHelper.getPanDirection(velocity: velocity)
        }
    
        // if selected brick
        if self.selectedBrickNode != nil {
    
            if sender.state == UIGestureRecognizerState.began{
                lastAngle = 0.0   // reset last angle
            }
    
            let translation = sender.translation(in: sender.view!)
            let anglePan = (self.panDirection == "horizontal") ?  GameHelper.deg2rad(deg: Float(translation.x)) : GameHelper.deg2rad(deg: Float(translation.y))
    
            let x:Float = (self.panDirection == "vertical" ) ? 1 : 0.0
            let y:Float = (self.panDirection == "horizontal" ) ?  1 : 0.0
    
            // calculate the angle change from last call
            let fraction = anglePan - lastAngle
            lastAngle = anglePan
    
            // perform rotation by difference to last angle
            selectedBrickNode.transform = SCNMatrix4Mult(selectedBrickNode.transform,SCNMatrix4MakeRotation(fraction,  x, y, 0.0))
    
            if(sender.state == UIGestureRecognizerState.ended) {
    
                // calculate angle to snap to 90 degree increments
                let finalRotation = GameHelper.rad2deg(rad:anglePan)
                let diff = finalRotation.truncatingRemainder(dividingBy: 90.0)
                var finalDiff = Float(0.0)
    
                switch diff {
                    case 45..<90 :
                        finalDiff = 90 - diff
                    case 0..<45 :
                        finalDiff = -diff
                    case -45..<0 :
                        finalDiff = abs(diff)
                    case -90 ..< -45 :
                        finalDiff = -90 - diff
                default:
                    print("do nothing")
                }
    
                // final transform to apply snap to closest 90deg increment
                let snapAngle = GameHelper.deg2rad(deg: finalDiff)
                selectedBrickNode.transform = SCNMatrix4Mult(selectedBrickNode.transform, SCNMatrix4MakeRotation(snapAngle, x, y, 0.0))
    
                // remove highlight from node and deselect
                self.selectedBrickNode?.geometry?.materials = [hlp.defaultMaterial]
                self.selectedBrickNode = nil
            }
        }
    
    }
    

    和 GameHelper.swift

    class GameHelper {
    
      static func rad2deg( rad:Float ) -> Float {
        return rad * (Float) (180.0 /  Double.pi)
      }
    
      static func deg2rad( deg:Float ) -> Float{
       return deg * (Float)(Double.pi / 180)
      }
    
      static func getPanDirection(velocity: CGPoint) -> String {
        var panDirection:String = ""
        if ( velocity.x > 0 && velocity.x > abs(velocity.y) || velocity.x < 0 && abs(velocity.x) > abs(velocity.y) ){
            panDirection = "horizontal"
        }
    
        if ( velocity.y < 0 && abs(velocity.y) > abs(velocity.x) || velocity.y > 0 &&  velocity.y  > abs(velocity.x)) {
            panDirection = "vertical"
        }
    
        return panDirection
      }
    
    }
    

相关问题