SceneKit-поворот SCNNode с жестом панорамирования на основе направления
Я пишу свой первый проект scenekit и пытаюсь использовать жест Pan для поворота простого объекта в моей сцене (объект-это простая форма куба L, импортированная из .файл dae, точка поворота установлена правильно).
Я прошел через множество решений 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 ответ:
После долгих исследований и нескольких часов битья головой о стену, я наконец нашел рабочее решение, надеюсь, что оно поможет кому-то с подобной целью.
Мой рабочий код:
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 } }
Comments