Tengo un SCNCamera en la posición(30,30,30) con un SCNLookAtConstraint sobre un objeto situado en la posición(0,0,0). Estoy tratando de llegar a la cámara para girar alrededor del objeto sobre una imaginaria esfera mediante Un UIPanGestureRecognizer, mientras que el mantenimiento de la radio entre la cámara y el objeto. Estoy asumiendo que debo uso de Cuaterniones proyecciones, pero mis conocimientos matemáticos en esta área es abismal. Mi conocido variables son x & y la traducción de + la radio estoy tratando de mantener. He escrito el proyecto de Swift, pero una respuesta en Objective-C sería igualmente aceptados (es de esperar que el uso de un estándar de Cocoa Touch Marco).

Donde:

private var cubeView : SCNView!;
private var cubeScene : SCNScene!;
private var cameraNode : SCNNode!;

Aquí está mi código para la configuración de la escena:

//setup the SCNView
cubeView = SCNView(frame: CGRectMake(0, 0, self.width(), 175));
cubeView.autoenablesDefaultLighting = YES;
self.addSubview(cubeView);

//setup the scene
cubeScene = SCNScene();
cubeView.scene = cubeScene;

//setup the camera
let camera = SCNCamera();
camera.usesOrthographicProjection = YES;
camera.orthographicScale = 9;
camera.zNear = 0;
camera.zFar = 100;

cameraNode = SCNNode();
cameraNode.camera = camera;
cameraNode.position = SCNVector3Make(30, 30, 30)  
cubeScene.rootNode.addChildNode(cameraNode)

//setup a target object
let box = SCNBox(width: 10, height: 10, length: 10, chamferRadius: 0);
let boxNode = SCNNode(geometry: box)
cubeScene.rootNode.addChildNode(boxNode)

//put a constraint on the camera
let targetNode = SCNLookAtConstraint(target: boxNode);
targetNode.gimbalLockEnabled = YES;
cameraNode.constraints = [targetNode];

//add a gesture recogniser
let gesture = UIPanGestureRecognizer(target: self, action: "panDetected:");
cubeView.addGestureRecognizer(gesture);

Y aquí está el código para el gesto reconocedor de manejo:

private var position: CGPoint!;

internal func panDetected(gesture:UIPanGestureRecognizer) {

    switch(gesture.state) {
    case UIGestureRecognizerState.Began:
        position = CGPointZero;
    case UIGestureRecognizerState.Changed:
        let aPosition = gesture.translationInView(cubeView);
        let delta = CGPointMake(aPosition.x-position.x, aPosition.y-position.y);

        //??? no idea...

        position = aPosition;
    default:
        break
    }
}

Gracias!

InformationsquelleAutor Danny Bravo | 2014-09-03

6 Comentarios

  1. 96

    Podría ayudar a descomponer el problema en subproblemas.

    La puesta en Escena

    Primero, pensar en cómo organizar la escena para permitir el tipo de movimiento que desee. Hable acerca de cómo mover la cámara como si se une a una esfera invisible. El uso de esa idea! En lugar de intentar trabajar las matemáticas para establecer su cameraNode.position hasta cierto punto en una imaginaria esfera, basta con pensar en lo que haría para mover la cámara si estuviera conectada a una esfera. Que es, simplemente gire la esfera.

    Si desea girar una esfera separada del resto de su contenido de la escena, se había adjuntarlo a un nodo independiente. Por supuesto, usted realmente no necesita insertar una la geometría de la esfera en su escena. Acaba de hacer un nodo cuyo position es concéntrico con el objeto de que usted quiere que su cámara a la órbita alrededor de, a continuación, conecte la cámara a un nodo hijo del nodo. A continuación, puede girar el nodo para mover la cámara. He aquí una pequeña demostración de que, ausente el desplazamiento de manejo de eventos de negocios:

    let camera = SCNCamera()
    camera.usesOrthographicProjection = true
    camera.orthographicScale = 9
    camera.zNear = 0
    camera.zFar = 100
    let cameraNode = SCNNode()
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 50)
    cameraNode.camera = camera
    let cameraOrbit = SCNNode()
    cameraOrbit.addChildNode(cameraNode)
    cubeScene.rootNode.addChildNode(cameraOrbit)
    
    //rotate it (I've left out some animation code here to show just the rotation)
    cameraOrbit.eulerAngles.x -= CGFloat(M_PI_4)
    cameraOrbit.eulerAngles.y -= CGFloat(M_PI_4*3)

    Aquí es lo que usted ve en la izquierda, y una visualización de cómo funciona en el derecho. Los cuadros de la esfera es cameraOrbit, y el cono verde es cameraNode.

    Gire SCNCamera nodo mirar un objeto alrededor de una esfera imaginaria

    Hay un par de bonos de este enfoque:

    • Usted no tiene que ajustar la inicial de la posición de la cámara en coordenadas Cartesianas. Sólo tiene que colocar en cualquiera que sea la distancia que desea lo largo del eje z. Desde cameraNode es un nodo hijo de cameraOrbit, su propia posición se mantiene constante — la cámara se mueve debido a la rotación de cameraOrbit.
    • Mientras que usted sólo desea que la cámara señaló en el centro de esta esfera imaginaria, usted no necesita mirar restricción. La cámara apunta en la dirección Z del espacio en — si se mueve en la dirección +Z, a continuación, gire el nodo padre, la cámara siempre a punto en el centro del nodo padre (es decir, el centro de rotación).

    Entrada De Gestión De

    Ahora que ya tienes tu escena diseñada por la rotación de la cámara, convirtiendo los eventos de entrada en la rotación es bastante fácil. Tan fácil, depende del tipo de control que busques:

    • Buscando arcball la rotación? (Es ideal para la manipulación directa, ya que se puede sentir como si usted está físicamente empujando a un punto en el objeto 3D.) Hay algunos preguntas y respuestas acerca de que ya en TAN — la mayoría de ellos utilizan GLKQuaternion. (ACTUALIZACIÓN: GLK tipos son «sorta», disponible en el Swift 1.2 /Xcode 6.3. Antes de esas versiones que usted puede hacer sus matemáticas en ObjC a través de un puente de encabezado.)
    • Para una alternativa más simple, usted puede asignar los ejes x y y de su gesto a los ángulos de cabeceo y guiñada de su nodo. No es tan fantástico como arcball rotación, pero es bastante fácil de implementar — todo lo que necesitas hacer es trabajar en los puntos a radianes conversión que cubre la cantidad de rotación que usted está después.

    De cualquier manera, usted puede saltarse algunos de los gestos aprendidos repetitivo y ganar un poco de práctica comportamientos interactivos mediante el uso de UIScrollView lugar. (No es que no hay utilidad para quedarse con gesto reconocedores-esto es un fácil de implementar alternativa).

    Una gota en la parte superior de su SCNView (sin poner otro punto de vista de su interior para desplazarse) y establecer su contentSize a un múltiplo de su tamaño de fotograma… luego durante el desplazamiento puede asignar el contentOffset a su eulerAngles:

    func scrollViewDidScroll(scrollView: UIScrollView) {
        let scrollWidthRatio = Float(scrollView.contentOffset.x / scrollView.frame.size.width)
        let scrollHeightRatio = Float(scrollView.contentOffset.y / scrollView.frame.size.height)
        cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * scrollWidthRatio
        cameraOrbit.eulerAngles.x = Float(-M_PI) * scrollHeightRatio
    }

    Por un lado, usted tiene que trabajar un poco más para infinito desplazamiento si desea girar sin cesar en una o ambas direcciones. En la otra, se obtiene buen desplazamiento de estilo de la inercia y el rebote de los comportamientos.

    • Gracias por esa gran respuesta. Me pasé horas y horas el cálculo de las coordenadas, pero luego por alguna razón SceneKit tiene un error con su posicionamiento, así que esto me salvó el día.
    • perdona que te moleste… si tienes tiempo y ganas, por favor considere este problema con tu fuerza mental puede: stackoverflow.com/questions/36190789/…
  2. 10

    Hola me encontré con el problema el otro día y la solución que se me ocurrió es bastante simple, pero funciona bien.

    Primero he creado mi cámara y añadido a mi la escena así:

        //create and add a camera to the scene
        cameraNode = [SCNNode node];
        cameraNode.camera = [SCNCamera camera];
        cameraNode.camera.automaticallyAdjustsZRange = YES;
        [scene.rootNode addChildNode:cameraNode];
    
        //place the camera
        cameraNode.position = SCNVector3Make(0, 0, 0);
        cameraNode.pivot = SCNMatrix4MakeTranslation(0, 0, -15); //the -15 here will become the rotation radius

    Luego me hizo un CGPoint slideVelocity variable de clase. Y creó un UIPanGestureRecognizer y un y en su devolución de llamada me puso el siguiente:

    -(void)handlePan:(UIPanGestureRecognizer *)gestureRecognize{
        slideVelocity = [gestureRecognize velocityInView:self.view];
    }

    Entonces tengo este método que es llamado cada fotograma. Tenga en cuenta que yo uso GLKit de matemáticas de cuaterniones.

    -(void)renderer:(id<SCNSceneRenderer>)aRenderer didRenderScene:(SCNScene *)scenie atTime:(NSTimeInterval)time {        
    //spin the camera according the the user's swipes
        SCNQuaternion oldRot = cameraNode.rotation;  //get the current rotation of the camera as a quaternion
        GLKQuaternion rot = GLKQuaternionMakeWithAngleAndAxis(oldRot.w, oldRot.x, oldRot.y, oldRot.z);  //make a GLKQuaternion from the SCNQuaternion
    
    //The next function calls take these parameters: rotationAngle, xVector, yVector, zVector
        //The angle is the size of the rotation (radians) and the vectors define the axis of rotation
        GLKQuaternion rotX = GLKQuaternionMakeWithAngleAndAxis(-slideVelocity.x/viewSlideDivisor, 0, 1, 0); //For rotation when swiping with X we want to rotate *around* y axis, so if our vector is 0,1,0 that will be the y axis
        GLKQuaternion rotY = GLKQuaternionMakeWithAngleAndAxis(-slideVelocity.y/viewSlideDivisor, 1, 0, 0); //For rotation by swiping with Y we want to rotate *around* the x axis.  By the same logic, we use 1,0,0
        GLKQuaternion netRot = GLKQuaternionMultiply(rotX, rotY); //To combine rotations, you multiply the quaternions.  Here we are combining the x and y rotations
        rot = GLKQuaternionMultiply(rot, netRot); //finally, we take the current rotation of the camera and rotate it by the new modified rotation.
    
    //Then we have to separate the GLKQuaternion into components we can feed back into SceneKit
        GLKVector3 axis = GLKQuaternionAxis(rot);
    float angle = GLKQuaternionAngle(rot);
    //finally we replace the current rotation of the camera with the updated rotation
        cameraNode.rotation = SCNVector4Make(axis.x, axis.y, axis.z, angle);
    //This specific implementation uses velocity.  If you don't want that, use the rotation method above just replace slideVelocity.
        //decrease the slider velocity
        if (slideVelocity.x > -0.1 && slideVelocity.x < 0.1) {
    slideVelocity.x = 0;
    }
    else {
    slideVelocity.x += (slideVelocity.x > 0) ? -1 : 1;
    }
    if (slideVelocity.y > -0.1 && slideVelocity.y < 0.1) {
    slideVelocity.y = 0;
    }
    else {
    slideVelocity.y += (slideVelocity.y > 0) ? -1 : 1;
    }
    }

    Este código da infinito Arcball de rotación con velocidad, que creo que es lo que usted está buscando. También, usted no necesita el SCNLookAtConstraint con este método. De hecho, que probablemente se ensucia, así que no lo hagas.

    • sigue aprendiendo … lo que el tipo y el valor de viewSlideDivisor?
    • Ah se me olvidó mencionar que! viewSlideDivisor es una constante float he definido basado en el tamaño de la pantalla. Es un valor afecta a lo grande de un efecto cada pase tiene.
    • ¿cómo podría evitar que ruede en este ejemplo? Estoy usando esto para el control de la cámara en torno a una nave espacial, pero el rollo (yo creo – im todavía bastante nuevo con 3d) de una manera que no esperaba.
    • roll puede no ser el término correcto, básicamente, quiere que el barco parece estar siempre en línea con el horizonte y la cámara gira alrededor de la nave. Parece ser algo de inclinación pasando y no estoy seguro de cómo solucionarlo.
    • Trate de modificar y mantener el seguimiento de EulerAngles lugar. Modificar la X y la Y los ángulos de Euler, pero no el Z ángulo.
    • He probado que funciona el modelo ya no se inclina, pero el control de la cámara no se siente tan natural. Parece que apple ha añadido a la órbita de la tornamesa como un proceso interactivo opción para el valor predeterminado de la cámara y que funciona a la perfección para mí…hasta que agregar un segundo nodo mi escena. Parece que el punto de pivote cambios dependiendo de cuántos nodos y donde se colocan.
    • Este video parece tener alguna información útil: developer.apple.com/videos/play/wwdc2017/604 Hacerlo manualmente como se sugiere en las respuestas a este post probablemente ya no es la forma recomendada de hacerlo. Usted debe mirar en el uso de la cámara limitaciones en su lugar.

  3. 7

    Si quieres implementar rickster la respuesta mediante un gesto de reconocimiento, usted tiene que guardar la información de estado como sólo se da una traducción en relación al comienzo del gesto. He añadido dos variables para mi clase

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

    Y aplicado en su girar el código de la siguiente manera:

    func handlePanGesture(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
    if (sender.state == .Ended) {
    lastWidthRatio = widthRatio % 1
    lastHeightRatio = heightRatio % 1
    }
    }
    • A veces se mueve en dirección opuesta a la de la panorámica
    • ¿Te fijas que?
  4. 5

    Tal vez esto podría ser útil para los lectores.

    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 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!)
    var widthRatio = Float(translation.x) / Float(gestureRecognize.view!.frame.size.width) + lastWidthRatio
    var heightRatio = Float(translation.y) / Float(gestureRecognize.view!.frame.size.height) + lastHeightRatio
    if (numberOfTouches==fingersNeededToPan) {
    // 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
    }
    }
    override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return .Landscape
    }
    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    //Release any cached data, images, etc that aren't in use.
    }
    }
  5. 3

    No hay necesidad de guardar el estado en cualquier lugar, pero el propio nodo.
    El código que utiliza algún tipo de proporción entre la anchura y se comporta extrañamente, cuando se desplaza hacia atrás y adelante varias veces, y el código aquí parece demasiado complicado.
    Se me ocurrió una diferente (y creo que la mejor solución para el gesto de reconocedores, basado en @rickster del enfoque.

    UIPanGestureRecognizer:

    @objc func handlePan(recognizer: UIPanGestureRecognizer) {
    let translation = recognizer.velocity(in: recognizer.view)
    cameraOrbit.eulerAngles.y -= Float(translation.x/CGFloat(panModifier)).radians
    cameraOrbit.eulerAngles.x -= Float(translation.y/CGFloat(panModifier)).radians
    }

    UIPinchGestureRecognizer:

    @objc func handlePinch(recognizer: UIPinchGestureRecognizer) {
    guard let camera = cameraOrbit.childNodes.first else {
    return
    }
    let scale = recognizer.velocity
    let z = camera.position.z - Float(scale)/Float(pinchModifier)
    if z < MaxZoomOut, z > MaxZoomIn {
    camera.position.z = z
    }
    }

    He utilizado velocidad, como con traducción cuando disminuya el toque seguiría siendo el mismo evento, causando la cámara a dar vueltas muy rápido, no lo que cabría esperar.

    panModifier y pinchModifier son simples constante números que usted puede utilizar para ajustar la capacidad de respuesta. He encontrado los valores óptimos para ser 100 y 15 respectivamente.

    MaxZoomOut y MaxZoomIn son constantes y son exactamente lo que parecen ser.

    Yo también uso una extensión Flotante para convertir grados a radianes y viceversa.

    extension Float {
    var radians: Float {
    return self * .pi / 180
    }
    var degrees: Float {
    return self  * 180 / .pi
    }
    }
  6. 1

    Después de tratar de implementar estas soluciones (en Objective-C) me di cuenta de que la Escena Kit de realidad que hace que este sea mucho más fácil que haciendo todo esto. SCNView tiene un dulce propiedad llamada allowsCameraControl que pone en el gesto adecuado reconocedores y mueve la cámara en consecuencia. El único problema es que no es la arcball rotación que usted está buscando, a pesar de que pueden ser fácilmente añadidos mediante la creación de un nodo hijo, el posicionamiento, donde tú quieras y dándole un SCNCamera. Por ejemplo:

        _sceneKitView.allowsCameraControl = YES; //_sceneKitView is a SCNView
    
    //Setup Camera
        SCNNode *cameraNode = [[SCNNode alloc]init];
    cameraNode.position = SCNVector3Make(0, 0, 1);
    SCNCamera *camera = [SCNCamera camera];
    //setup your camera to fit your specific scene
        camera.zNear = .1;
    camera.zFar = 3;
    cameraNode.camera = camera;
    [_sceneKitView.scene.rootNode addChildNode:cameraNode];
    • No estoy claro en lo que allowsCameraControl va a hacer para ayudar si algunas específicas de la cámara de comportamiento, tales como arcball rotación deseada; no, como yo lo entiendo, afectar a cualquiera de las cámaras en la escena. Podrías elaborar un poco más sobre cómo usted está haciendo uso de este?
    • mi entendimiento es que se manipula el punto de vista actual de la SCNView. A partir de la documentación, «Esta acción no modifica de la cámara de los objetos ya existentes en la escena de gráfico o de los nodos que contienen los mismos. El valor predeterminado de esta propiedad es NO.» Cuando se agrega allowsCameraControl a un SCNView como lo hice anteriormente, la escena se inicializa y muestra de acuerdo con cualquier Nodo contiene su cámara, ya que de lo contrario serían. Scenekit automáticamente (y detrás de las escenas, las funciones que no aparecen en el código), se añade pan/gesto de reconocedores y mueve la cámara de acuerdo a los gestos.
    • allowsCameraControl es esencialmente de depuración de la funcionalidad que se opone a cualquier tipo de personalización
    • Esta no es una solución viable para lo que fue solicitado. Como @AlfieHanssen dijo, es en esencia, para la depuración, y ofrece absolutamente ningún control sobre lo que el usuario puede o no puede hacer.

Dejar respuesta

Please enter your comment!
Please enter your name here