SCNCamera ограничивает вращение дугового шара

У меня есть настройка сцены с помощью SCNCamera, которая вращается вокруг объекта.

Каков наилучший способ ограничить степень вращения камеры вокруг объекта?

Пример: вместо того, чтобы вращаться вокруг всей сферы, как бы я ограничил вращение одним полушарием?

Моя первая попытка состояла в том, чтобы посмотреть, есть ли зажимы для .allowsCameraControl. Ничего не нашел.

Затем я попытался адаптировать С# Unity: скрипт орбиты мыши, но не повезло.

Некоторые указатели на то, как подойти или решить это, были бы замечательными.

Шаблон Arcball благодаря этому ответу .

var lastWidthRatio: Float = 0
var lastHeightRatio: Float = 0

let camera = SCNCamera()
let cameraNode = SCNNode()
let cameraOrbit = SCNNode()

override func viewDidLoad() {
    super.viewDidLoad()

    // create a new scene
    let scene = SCNScene(named: "art.scnassets/ship.scn")!

    // create and add a camera to the scene

    camera.usesOrthographicProjection = true
    camera.orthographicScale = 9
    camera.zNear = 0
    camera.zFar = 100

    cameraNode.position = SCNVector3(x: 0, y: 0, z: 50)
    cameraNode.camera = camera

    cameraOrbit.addChildNode(cameraNode)
    scene.rootNode.addChildNode(cameraOrbit)

    // retrieve the ship node
    let ship = scene.rootNode.childNodeWithName("ship", recursively: true)!

    // retrieve the SCNView
    let scnView = self.view as! SCNView

    // set the scene to the view
    scnView.scene = scene

    // add a tap gesture recognizer
    let gesture = UIPanGestureRecognizer(target: self, action: "panDetected:");
    scnView.addGestureRecognizer(gesture);
}


func panDetected(sender: UIPanGestureRecognizer) {
    let translation = sender.translationInView(sender.view!)
    let widthRatio = Float(translation.x) / Float(sender.view!.frame.size.width) + lastWidthRatio
    let heightRatio = Float(translation.y) / Float(sender.view!.frame.size.height) + lastHeightRatio
    self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
    self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio

    print(Float(-2 * M_PI) * widthRatio)
    if (sender.state == .Ended) {
        lastWidthRatio = widthRatio % 1
        lastHeightRatio = heightRatio % 1
    }
}

person Magrafear    schedule 28.11.2015    source источник
comment
Я не думаю, что операция по модулю 1 даст желаемый результат (widthRatio % 1). ШиринаRatio будет варьироваться от -1 до 1, но когда пользователь переходит за ограничение, значение может измениться с -0,9 до, скажем, -1,2. Применяется модуль, в результате чего получается значение 0,2, вызывающее резкий скачок на другую сторону сферы. Один из способов обойти это было бы добавить 2, если значение падает ниже -1, и вычесть 2, если конец вращения выше 1. Используя этот метод, перемещение от позиции -0,9 до -1,2 приведет к значению 0,8 , что я считаю желаемым результатом.   -  person Dave Ruske    schedule 17.01.2016


Ответы (2)


Похоже, вы почти у цели, используя только код @Rickster из процитированного вами ответа.

Изменение, которое вы могли бы внести, будет в следующих строках:

self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio

которые неявно позволяют тангажу и рысканью покрывать всю сферу. Вот где вы можете сделать свое ограничение. Например, вместо того, чтобы изменять высоту тона (eulerAngles.x) от 0 до -π, вы могли бы сделать

self.cameraOrbit.eulerAngles.x = Float(-M_PI_2) + Float(-M_PI_2) * heightRatio

чтобы плавно изменяться между -π/2 и -π, используя полноэкранную вертикальную прокрутку, чтобы охватить этот диапазон. Или вы можете установить жесткие минимальные/максимальные ограничения/проверки в этих двух строках, чтобы ограничить конкретную область земного шара.

(Изменить, чтобы устранить комментарий по инерции)

Для демпфирования вращения или инерции я бы подошел к этому, используя встроенную физику SceneKit, и, возможно, поместил камеру на невидимый (без геометрии) SCNNode. Этот узел камеры становится карданом, как в этом проекте: Интерактивный двухметровый глобус, полностью созданный в RubyMotion и SceneKit.

Затем виртуальный карданный подвес получает SCNPhysicsBody (вы добавляете это, он не поставляется с ним по умолчанию) с некоторыми damping. Или, возможно, вы наложили физику на свой центральный объект и придали этому объекту немного angularDamping.

person Hal Mueller    schedule 30.11.2015
comment
Отлично! Большое спасибо за ответ. После того, как я внес сдачу и посмотрел распечатку под разными углами, все обрело смысл и щелкнуло. - person Magrafear; 01.12.2015
comment
У меня есть дополнительный вопрос, если возможно @hal-mueller? Я пытаюсь добавить инерции к вращению дугового шара. let rotate = SCNAction.rotateByAngle(CGFloat(Float(-M_PI_2) + Float(-M_PI_2) * widthRatio), aroundAxis:SCNVector3Make(0 , 0, 1), duration: NSTimeInterval(1)) Это самое близкое к правильному, что у меня есть. Но поворот, кажется, не идет по дуге. Спасибо - person Magrafear; 12.12.2015

Возможно, это будет полезно для читателей.

    class GameViewController: UIViewController {

    var cameraOrbit = SCNNode()
    let cameraNode = SCNNode()
    let camera = SCNCamera()


    //HANDLE PAN CAMERA
    var lastWidthRatio: Float = 0
    var lastHeightRatio: Float = 0.2
    var WidthRatio: Float = 0
    var HeightRatio: Float = 0.2
    var fingersNeededToPan = 1
    var maxWidthRatioRight: Float = 0.2
    var maxWidthRatioLeft: Float = -0.2
    var maxHeightRatioXDown: Float = 0.02
    var maxHeightRatioXUp: Float = 0.4

    //HANDLE PINCH CAMERA
    var pinchAttenuation = 20.0  //1.0: very fast ---- 100.0 very slow
    var lastFingersNumber = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        // create a new scene
        let scene = SCNScene(named: "art.scnassets/ship.scn")!

        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = SCNLightTypeOmni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)

        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = SCNLightTypeAmbient
        ambientLightNode.light!.color = UIColor.darkGrayColor()
        scene.rootNode.addChildNode(ambientLightNode)

    //Create a camera like Rickster said
        camera.usesOrthographicProjection = true
        camera.orthographicScale = 9
        camera.zNear = 1
        camera.zFar = 100

        cameraNode.position = SCNVector3(x: 0, y: 0, z: 50)
        cameraNode.camera = camera
        cameraOrbit = SCNNode()
        cameraOrbit.addChildNode(cameraNode)
        scene.rootNode.addChildNode(cameraOrbit)

        //initial camera setup
        self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * lastWidthRatio
        self.cameraOrbit.eulerAngles.x = Float(-M_PI) * lastHeightRatio

        // retrieve the SCNView
        let scnView = self.view as! SCNView

        // set the scene to the view
        scnView.scene = scene

        //allows the user to manipulate the camera
        scnView.allowsCameraControl = false  //not needed

        // add a tap gesture recognizer
        let panGesture = UIPanGestureRecognizer(target: self, action: "handlePan:")
        scnView.addGestureRecognizer(panGesture)

        // add a pinch gesture recognizer
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: "handlePinch:")
        scnView.addGestureRecognizer(pinchGesture)
    }

    func handlePan(gestureRecognize: UIPanGestureRecognizer) {

        let numberOfTouches = gestureRecognize.numberOfTouches()

        let translation = gestureRecognize.translationInView(gestureRecognize.view!)

        if (numberOfTouches==fingersNeededToPan) {

           widthRatio = Float(translation.x) / Float(gestureRecognize.view!.frame.size.width) + lastWidthRatio
           heightRatio = Float(translation.y) / Float(gestureRecognize.view!.frame.size.height) + lastHeightRatio

            //  HEIGHT constraints
            if (heightRatio >= maxHeightRatioXUp ) {
                heightRatio = maxHeightRatioXUp
            }
            if (heightRatio <= maxHeightRatioXDown ) {
                heightRatio = maxHeightRatioXDown
            }


            //  WIDTH constraints
            if(widthRatio >= maxWidthRatioRight) {
                widthRatio = maxWidthRatioRight
            }
            if(widthRatio <= maxWidthRatioLeft) {
                widthRatio = maxWidthRatioLeft
            }

            self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
            self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio

            print("Height: \(round(heightRatio*100))")
            print("Width: \(round(widthRatio*100))")


            //for final check on fingers number
            lastFingersNumber = fingersNeededToPan
        }

        lastFingersNumber = (numberOfTouches>0 ? numberOfTouches : lastFingersNumber)

        if (gestureRecognize.state == .Ended && lastFingersNumber==fingersNeededToPan) {
            lastWidthRatio = widthRatio
            lastHeightRatio = heightRatio
            print("Pan with \(lastFingersNumber) finger\(lastFingersNumber>1 ? "s" : "")")
        }
    }

    func handlePinch(gestureRecognize: UIPinchGestureRecognizer) {
        let pinchVelocity = Double.init(gestureRecognize.velocity)
        //print("PinchVelocity \(pinchVelocity)")

        camera.orthographicScale -= (pinchVelocity/pinchAttenuation)

        if camera.orthographicScale <= 0.5 {
            camera.orthographicScale = 0.5
        }

        if camera.orthographicScale >= 10.0 {
            camera.orthographicScale = 10.0
        }

    }
person Lorenzo Andraghetti    schedule 09.03.2016