0
votes

I'm a new programmer working on a simple demo iOS program which I use SceneKit to render the scene.

I want to rotate the camera to see different perspective of the scene. But the original camera control is little bit tricky. Especially when I want to rotate the scene in one direction. e.g. if I swipe from right to left and the camera goes from right to left, then go downwards, then go upwards, then go downwards again, finally it goes left again.

What I want to achieve is that when I swipe from right to the left, the camera rotate around the z-axis. And when I swipe up and down my camera just move up and down. And I can also zoom in and out. Additionally I want to set a constraint so that the camera won't go underground.

So I come up with an idea that I fix the camera to look at the object that I focus on. Then place the object in the center of the scene. And I want to rotate the scene just around z-axis when I swipe the screen.

I assume that I use panGestureRecognizer and translate the position change to a rotation order.But how can I achieve that?

Or you have some other idea to get this results? Here's a simple version of what I write so far. I've tried using UIPanGestureRecognizer but it didn't work, so I delete it and set allowCameraControl = true

    let sceneView = SCNView(frame: self.view.frame)
    self.view.addSubview(sceneView)

    let scene = SCNScene()
    sceneView.scene = scene

    let camera = SCNCamera()
    let cameraNode = SCNNode()
    cameraNode.camera = camera
    cameraNode.position = SCNVector3(x: -5.0, y: 5.0, z: 5.0)

    let light = SCNLight()
    light.type = SCNLight.LightType.spot
    light.spotInnerAngle = 30
    light.spotOuterAngle = 80
    light.castsShadow = true
    let lightNode = SCNNode()
    lightNode.light = light
    lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)

    let ambientLight = SCNLight()
    ambientLight.type = SCNLight.LightType.ambient
    ambientLight.color = UIColor(red: 0.2, green: 0.2,  blue: 0.2, alpha: 1.0)
    cameraNode.light = ambientLight

    let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
    let cubeNode = SCNNode(geometry: cubeGeometry)

    let planeGeometry = SCNPlane(width: 50.0, height: 50.0)
    let planeNode = SCNNode(geometry: planeGeometry)
    planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
    planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)

    cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
    let constraint = SCNLookAtConstraint(target: cubeNode)
    constraint.isGimbalLockEnabled = true
    cameraNode.constraints = [constraint]
    lightNode.constraints = [constraint]

    scene.rootNode.addChildNode(lightNode)
    scene.rootNode.addChildNode(cameraNode)
    scene.rootNode.addChildNode(cubeNode)
    scene.rootNode.addChildNode(planeNode)

    sceneView.allowsCameraControl = true
1

1 Answers

0
votes

I solved this problem by using Spherical coordinate system.

General idea is to get the start position and the angle, then transform it to spherical coordinate system. Then you do rotation by just changing the two angle.

It's even easier to use spherical coordinate system to deal with pinch gesture. You just have to change the radius.

here's some code that I use.

func handlePan(_ gestureRecognize: UIPanGestureRecognizer) {
    // retrieve scene
    let carView = self.view as! SCNView

    // save node data and pan gesture data
    let cameraNode = carView.scene?.rootNode.childNode(withName: "Camera", recursively: true)!
    //let translation = gestureRecognize.translation(in: gestureRecognize.view!)

    let speed = gestureRecognize.velocity(in: gestureRecognize.view!)
    var speedX = sign(Float(speed.x)) * Float(sqrt(abs(speed.x)))
    var speedY = sign(Float(speed.y)) * Float(sqrt(abs(speed.y)))
    if speedX.isNaN {speedX = 0}
    if speedY.isNaN {speedY = 0}

    // record start value
    let cameraXStart = cameraNode!.position.x
    let cameraYStart = cameraNode!.position.y
    let cameraZStart = cameraNode!.position.z - 1.0

    let cameraAngleStartZ = cameraNode!.eulerAngles.z
    let cameraAngleStartX = cameraNode!.eulerAngles.x
    let radiusSquare = cameraXStart * cameraXStart + cameraYStart * cameraYStart + cameraZStart * cameraZStart

    // calculate delta value
    let deltaAngleZ = -0.003 * Float(speedX)
    let deltaAngleX = -0.003 * Float(speedY)

    // get new Value
    var cameraNewAngleZ = cameraAngleStartZ + deltaAngleZ
    var cameraNewAngleX = cameraAngleStartX + deltaAngleX

    if cameraNewAngleZ >= 100 * Float.pi {
        cameraNewAngleZ = cameraNewAngleZ - 100 * Float.pi
    } else if cameraNewAngleZ < -100 * Float.pi {
        cameraNewAngleZ = cameraNewAngleZ + 100 * Float.pi
    } else {
        // set limit
        if cameraNewAngleX > 1.4 {
            cameraNewAngleX = 1.4
        } else if cameraNewAngleX < 0.1 {
            cameraNewAngleX = 0.1
        }
        // use angle value to get position value
        let cameraNewX =  sqrt(radiusSquare) * cos(cameraNewAngleZ - Float.pi/2) * cos(cameraNewAngleX - Float.pi/2)
        let cameraNewY =  sqrt(radiusSquare) * sin(cameraNewAngleZ - Float.pi/2) * cos(cameraNewAngleX - Float.pi/2)
        let cameraNewZ = -sqrt(radiusSquare) * sin(cameraNewAngleX - Float.pi/2) + 1

        if cameraNode?.camera?.usesOrthographicProjection == false {
            cameraNode?.position = SCNVector3Make(cameraNewX, cameraNewY, cameraNewZ)
            cameraNode?.eulerAngles = SCNVector3Make(cameraNewAngleX, 0, cameraNewAngleZ)
        }
        else if cameraNode?.camera?.usesOrthographicProjection == true {
            cameraNode?.position = SCNVector3Make(0, 0, 10)
            cameraNode?.eulerAngles = SCNVector3Make(0, 0, cameraNewAngleZ)
        }
    }
}