3

For a voxel art app, the goal is to let users move and rotate a camera in a SceneKit scene then tap to place a block.

The code below lets a user rotate a camera by panning. After the gesture ends, we move an existing block so it is -X units on the camera's Z-axis (i.e., -X units in front of the camera).

cameraNode is the scene's point of view and is a child of userNode. When the user moves a joystick, we update the position of userNode.

Question: Other SO posts manipulate camera nodes by applying a transform instead of changing the rotation and position properties. Is one approach better than the other?

func sceneViewPannedOneFinger(sender: UIPanGestureRecognizer) {
    // Get pan distance & convert to radians
    let translation = sender.translationInView(sender.view!)
    var xRadians = GLKMathDegreesToRadians(Float(translation.x))
    var yRadians = GLKMathDegreesToRadians(Float(translation.y))

    // Get x & y radians
    xRadians = (xRadians / 6) + curXRadians
    yRadians = (yRadians / 6) + curYRadians

    // Limit yRadians to prevent rotating 360 degrees vertically
    yRadians = max(Float(-M_PI_2), min(Float(M_PI_2), yRadians))

    // Set rotation values to avoid Gimbal Lock
    cameraNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: yRadians)
    userNode.rotation = SCNVector4(x: 0, y: 1, z: 0, w: xRadians)

    // Save value for next rotation
    if sender.state == UIGestureRecognizerState.Ended {
        curXRadians = xRadians
        curYRadians = yRadians
    }

    // Set preview block
    setPreviewBlock()
}

private func setPreviewBlock(var futurePosition: SCNVector3 = SCNVector3Zero, reach: Float = 8) -> SCNVector3 {
    // Get future position
    if SCNVector3EqualToVector3(futurePosition, SCNVector3Zero) {
        futurePosition = userNode.position
    }

    // Get current position after accounting for rotations
    let hAngle = Float(cameraNode.rotation.w * cameraNode.rotation.x)
    let vAngle = Float(userNode.rotation.w * userNode.rotation.y)
    var position = getSphericalCoords(hAngle, t: vAngle, r: reach)
    position += userNode.position

    // Snap position to grid
    position = position.rounded()

    // Ensure preview block never dips below floor
    position.y = max(0, position.y)

    // Return if snapped position hasn't changed
    if SCNVector3EqualToVector3(position, previewBlock.position) {
        return position
    }

    // If here, animate preview block to new position
    SCNTransaction.begin()
    SCNTransaction.setAnimationDuration(AnimationTime)
    previewBlock.position = position
    SCNTransaction.commit()

    // Return position
    return position
}

func getSphericalCoords(s: Float, t: Float, r: Float) -> SCNVector3 {
    return SCNVector3(-(cos(s) * sin(t) * r),
                      sin(s) * r,
                      -(cos(s) * cos(t) * r))
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439

0 Answers0