Rotation nœud SCNCamera regardant un objet autour d'une sphère imaginaire

j'ai un SCNCamera en position(30,30,30) avec un SCNLookAtConstraint sur un objet situé en position(0,0). J'essaie de faire tourner la caméra autour de l'objet sur une sphère imaginaire en utilisant un UIPanGestureRecognizer, tout en maintenant le rayon entre la caméra et l'objet. Je suppose que je devrais utiliser des projections de Quaternion mais mes connaissances en mathématiques dans ce domaine sont abyssales. Mes variables connues sont X & Y translation + le rayon que j'essaie de garder. J'ai écrit le projet dans Swift, mais une réponse dans L'Objectif-C serait également acceptée (en utilisant, espérons-le, un cadre standard de Cocoa Touch).

où:

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

Voici mon code pour la mise en scène:

// 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);

et voici le code pour la manipulation de reconnaissance de geste:

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

Merci!

28
demandé sur Gigantic 2014-09-04 02:43:19

5 réponses

il pourrait aider à décomposer votre problème en sous-problèmes.

mise en Scène

tout d'Abord, pensez à la façon d'organiser votre scène pour activer le genre de mouvement que vous voulez. Vous parlez de déplacer la caméra comme si elle était attachée à une sphère invisible. Utilisez cette idée! Au lieu d'essayer de calculer le calcul pour mettre votre cameraNode.position à un certain point sur une sphère imaginaire, pensez juste à ce que vous feriez pour déplacer la caméra si elle était attaché à une sphère. C'est, il suffit de tourner la sphère.

si vous vouliez tourner une sphère séparément du reste du contenu de votre scène, vous l'attacheriez à un noeud séparé. Bien sûr, vous n'avez pas besoin d'insérer un géométrie sphère dans votre scène. Il suffit de faire un noeud dont position est concentrique avec l'objet que vous voulez que votre caméra tourne autour, puis attachez la caméra à un noeud enfant de ce noeud. Alors vous pouvez tourner ce noeud pour déplacer la caméra. Voici une démonstration rapide de cela, en l'absence de l'activité de gestion d'événements par défilement:

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)

voici ce que vous voyez à gauche, et une visualisation de comment cela fonctionne à droite. La sphère à damier est cameraOrbit , et le cône vert est cameraNode .

camera rotate around cube camera rotate visualization

il y a quelques bonus à cette approche:

  • vous n'avez pas à définir la position initiale de la caméra en coordonnées cartésiennes. Il suffit de le placer à n'importe quelle distance que vous voulez le long de l'axe Z. Depuis cameraNode est un nœud enfant de cameraOrbit , sa propre position reste constante-la caméra se déplace en raison de la rotation de cameraOrbit .
  • tant que vous voulez simplement que la caméra soit pointée au centre de cette sphère imaginaire, vous n'avez pas besoin d'une contrainte de regard. La caméra pointe dans la direction Z de l'espace c'est -- si vous le déplacez dans la direction +Z, puis tournez le noeud parent, la caméra pointera toujours au centre du noeud parent (c.-à-d. le centre de rotation).

Traitement Des Données

maintenant que vous avez votre scène architecturée pour la rotation de la caméra, transformer les événements d'entrée en rotation est assez facile. La facilité dépend du type de contrôle que vous recherchez:

  • cherche arcball la rotation? (C'est parfait pour la manipulation directe, puisque vous pouvez avoir l'impression de pousser physiquement un point sur l'objet 3D.) Il y a quelques questions et réponses à ce sujet déjà sur SO -- la plupart d'entre eux utilisent GLKQuaternion . ( mise à jour: les types GLK sont" sorta " disponibles en Swift 1.2 / Xcode 6.3. Avant ces versions, vous pouvez faire vos maths en ObjC via un en-tête de pont.)
  • pour une alternative plus simple, vous pouvez simplement cartographier le x et axes de y de votre geste aux angles de lacet et de tangage de votre noeud. Ce n'est pas aussi chic que la rotation arcball, mais c'est assez facile à mettre en œuvre -- tout ce que vous devez faire est de travailler sur une conversion points-à-radians qui couvre la quantité de rotation que vous êtes après.

dans tous les cas, vous pouvez sauter une partie du boilerplate de reconnaissance de geste et d'obtenir quelques comportements interactifs pratiques en utilisant UIScrollView à la place. (Non pas qu'il ne soit pas utile de rester reconnaissance gestuelle -- c'est juste une alternative facile à mettre en œuvre.)

déposez - en un sur votre SCNView (sans mettre une autre vue à l'intérieur) et réglez son contentSize à un multiple de sa taille de cadre... ensuite, pendant le défilement, vous pouvez faire la carte du contentOffset à votre 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
}

, d'une part, vous avez à faire un peu plus de travail pour infini défilement si vous voulez spin sans fin dans une ou les deux directions. De l'autre, vous obtenez de l'inertie de style scroll et des comportements de rebond.

83
répondu rickster 2017-05-23 12:10:30

Hey j'ai rencontré le problème l'autre jour et la solution que j'ai trouvée est assez simple mais fonctionne bien.

J'ai d'abord créé mon appareil photo et je l'ai ajouté à ma scène comme ceci:

    // 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

puis j'ai fait une variable de classe CGPoint slideVelocity . Et a créé un UIPanGestureRecognizer et un et dans son rappel j'ai mis ce qui suit:

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

alors j'ai cette méthode qui s'appelle chaque cadre. Notez que j'utilise GLKit pour quaternion mathématiques.

-(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;
    }
}

ce code donne la rotation infinie Arcball avec la vitesse, qui je crois est ce que vous cherchez. De plus, vous n'avez pas besoin du SCNLookAtConstraint avec cette méthode. En fait, ça va probablement tout gâcher, alors ne fais pas ça.

9
répondu WolfLink 2014-09-18 05:06:29

si vous voulez mettre en œuvre la réponse de rickster en utilisant un reconnaisseur de geste, vous devez sauvegarder les informations d'état car vous ne recevrez une traduction relative au début du geste. J'ai ajouté deux Var à ma classe

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

et a mis en œuvre son code de rotation comme suit:

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
    }
}
6
répondu JuJoDi 2015-03-08 21:40:06

peut-être cela pourrait être utile pour les lecteurs.

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.
}
}
4
répondu Lorenzo Andraghetti 2016-03-09 09:06:38

après avoir essayé de mettre en œuvre ces solutions (dans Objective-C) j'ai réalisé que Scene Kit rend en fait beaucoup plus facile que de faire tout cela. SCNView a une propriété douce appelée allowsCameraControl qui met dans le geste approprié des reconnaisseurs et déplace la caméra en conséquence. Le seul problème est que ce n'est pas la rotation d'arcball que vous recherchez, bien que cela puisse être facilement ajouté en créant un noeud enfant, en le positionnant où vous voulez, et je lui donne un SCNCamera. Par exemple:

    _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];
0
répondu sts54 2016-01-29 18:11:02