Search code examples
swiftscenekituigesturerecognizer

Building a 3D cube in iOS with two gestures


I can programmatically create a 3D cube in iOS using the code below. However, I need to use gestures to define its size. With a pinch gesture I'd like to build a base of cube in XZ axis. And with a drag gesture I'd like to build a height of cube in Y axis.

The question is: How to construct a 3D cube using these two gestures?

enter image description here

Here's my code:

import UIKit
import SceneKit

class GameViewController: UIViewController {
    var scnView: SCNView!
    var scnScene: SCNScene!
    var cameraNode: SCNNode!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setupView()
        self.setupScene()
        self.setupCamera()
        self.spawnShape()
    }
    override func shouldAutorotate() -> Bool {
        return true
    }
    override func prefersStatusBarHidden() -> Bool {
        return true
    }
    func setupView() {
        scnView = self.view as! SCNView
        scnView.allowsCameraControl = true
        scnView.autoenablesDefaultLighting = true
    }
    func setupScene() {
        scnScene = SCNScene()
        scnView.scene = scnScene
        scnScene.background.contents = UIImage(named: "bgTexture.png")
    }  
    func setupCamera() {
        cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 12)
        scnScene.rootNode.addChildNode(cameraNode)
    }    
    func spawnShape() {
        var geometry: SCNGeometry = SCNBox(width: 1.0, 
                                          height: 1.0, 
                                          length: 1.0, 
                                   chamferRadius: 0.1)
        let geometryNode = SCNNode(geometry: geometry)
        scnScene.rootNode.addChildNode(geometryNode)
    }
}   

Here's how I tried to implement UIPinchGestureRecognizer() and UIPanGestureRecognizer().

var pinchGesture = UIPinchGestureRecognizer()
var dragGesture = UIPanGestureRecognizer()

override func viewDidLoad() {
    super.viewDidLoad()
    setupView()
    setupScene()
    setupCamera()
    spawnShape()

    self.scnView.userInteractionEnabled = true
    self.scnView.multipleTouchEnabled = true

    pinchGesture = UIPinchGestureRecognizer(target: self, 
                                            action: #selector(pinchRecognized))
    dragGesture = UIPanGestureRecognizer(target: self, 
                                         action: #selector(panRecognized))
    self.scnView.addGestureRecognizer(self.pinchGesture)
    self.scnView.addGestureRecognizer(self.dragGesture)
}

@IBAction func pinchRecognized(pinch: UIPinchGestureRecognizer) { ... }
@IBAction func panRecognized(pan: UIPanGestureRecognizer) { ... }

Solution

  • You can use UIGestureRecognizers to accomplish what you are looking for.

    in your ViewDidLoad() add your recognizer:

        // Add Gesture recognizers
        let pinch = UIPinchGestureRecognizer(target: self, action: #selector(scaleSizeOfGeometry(sender:)))
        scnView.addGestureRecognizer(pinch)
    

    Incremental scaling in the XZ directions can now be accomplished by using:

    func scaleSizeOfGeometry(sender: UIPinchGestureRecognizer) {
        let scale = Float(sender.scale)
        referenceToGeometry?.scale.x *= scale
        referenceToGeometry?.scale.z *= scale
        sender.scale = 1.0
    }
    

    You have to reference the geometry you want to scale somehow. For example with a variable referenceToGeometry : SCNNode? that is set in your spawnGeometry():

    referenceToGeometry = geometryNode
    

    The UIPanGestureRecognizer is a bit more tricky since you can use a velocity or a translation to scale in the Y direction. In your viewDidLoad() add the gesture recognizer:

    let pan = UIPanGestureRecognizer(target: self, action: #selector(scaleHeightOfGeometry(sender:)))
    scnView.addGestureRecognizer(pan)
    

    And in your function use either the velocity or the translation:

    func scaleHeightOfGeometry(sender: UIPanGestureRecognizer) {
        // y : sender.translation(in: self.scnView).y
        // v : sender.velocity(in: self.scnView).y
    }
    

    You will have to extract a multiplier to your own liking from either of these. For example by storing the y[i-1] and checking the difference between the two, than adding the difference to the height of your geometry and storing the newValue as the y[i-1]th value.

    EDIT:

    To add to the post, you can use UIGestureRecognizer states for more advanced/finetuned versions of this.