[Swift] ARKitのモデルを動かす、回転させる、拡大縮小させる

2019年6月14日ITARKit, Swift

平面の検出ができてから、ARオブジェクトをタップで表示させて、、、

そのオブジェクトを縦横に動かしたり、回転させたり、拡大縮小できるアプリを作成しました。

全てGestureRecognizerを使っています。

オブジェクトを縦横に動かすにはLongPressを一旦使っているのですが、このままだとユーザーに説明しないと使い方がわからないですね。

ARアプリを作っていて思うのですが、従来のアプリでは2Dで考えていたのが、ARでは3D、すなわりZ座標を考慮する必要があります。

従来のアプリの延長線で考えていてはあかんな、と思いました。

GestureViewController.swift

import UIKit
import ARKit
import MBProgressHUD

class GestureViewController: UIViewController {

    @IBOutlet weak var sceneView: ARSCNView!
    let configuration = ARWorldTrackingConfiguration()
    
    private var mBProgressHUD: MBProgressHUD!

    private var newAngleY :Float = 0.0
    private var newAngleX :Float = 0.0
    private var currentAngleX :Float = 0.0
    private var currentAngleY :Float = 0.0
    private var localTranslatePosition :CGPoint!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.mBProgressHUD = MBProgressHUD.showAdded(to: self.sceneView, animated: true)
        self.mBProgressHUD.label.text = "平面検出中"
        
        sceneView.delegate = self
        
        // ARに内の座標軸と点群表示
        self.sceneView.debugOptions = [.showWorldOrigin, .showFeaturePoints]
        // fpsなどの表示
        sceneView.showsStatistics = true
        // 全方向光源ON
        sceneView.autoenablesDefaultLighting = true
        
        self.registerGestureRecognizer()
    }
    
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        configuration.planeDetection = .horizontal
        
        sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        sceneView.session.pause()
    }
    
    func registerGestureRecognizer() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
        self.sceneView.addGestureRecognizer(tapGestureRecognizer)
        
        let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinched))
        self.sceneView.addGestureRecognizer(pinchGestureRecognizer)
        
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panned))
        self.sceneView.addGestureRecognizer(panGestureRecognizer)
        
        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector
            (longPressed))
        self.sceneView.addGestureRecognizer(longPressGestureRecognizer)
    }
    
    @objc func tapped(recognizer: UITapGestureRecognizer) {
        guard let tappedView = recognizer.view as? ARSCNView else { return }
        
        let touchLocation = recognizer.location(in: tappedView)
        let hitTestResults = tappedView.hitTest(touchLocation, types: .existingPlane)
        if let hitTest = hitTestResults.first {
            let roboScene = SCNScene(named: "art.scnassets/robo.scn")!
            guard let roboNode = roboScene.rootNode.childNode(withName: "roboParentNode", recursively: true) else { return }
            roboNode.name = "robo"
            roboNode.position = SCNVector3(hitTest.worldTransform.columns.3.x,hitTest.worldTransform.columns.3.y,hitTest.worldTransform.columns.3.z)
            self.sceneView.scene.rootNode.addChildNode(roboNode)
        }
    }
    
    @objc func pinched(recognizer: UIPinchGestureRecognizer) {
        if recognizer.state == .changed {
            
            self.sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
                if node.name == "robo" {
                    let pinchScaleX = Float(recognizer.scale) * node.scale.x
                    let pinchScaleY = Float(recognizer.scale) * node.scale.y
                    let pinchScaleZ = Float(recognizer.scale) * node.scale.z
                    
                    node.scale = SCNVector3(pinchScaleX,pinchScaleY,pinchScaleZ)
                    recognizer.scale = 1
                }
            }
        }
    }
    
    @objc func panned(recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .changed:
            guard let pannedView = recognizer.view as? ARSCNView else { return }
            let translation = recognizer.translation(in: pannedView)
            
            self.sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
                if node.name == "robo" {
                    self.newAngleX = Float(translation.y) * (Float)(Double.pi)/180
                    self.newAngleX += self.currentAngleX
                    self.newAngleY = Float(translation.x) * (Float)(Double.pi)/180
                    self.newAngleY += self.currentAngleY
                    node.eulerAngles.x = self.newAngleX
                    node.eulerAngles.y = self.newAngleY
                }
            }

        case .ended:
            self.currentAngleX = self.newAngleX
            self.currentAngleY = self.newAngleY
        default:
            break
        }
    }
    
    @objc func longPressed(recognizer: UILongPressGestureRecognizer) {
        guard let longPressedView = recognizer.view as? ARSCNView else { return }
        let touch = recognizer.location(in: longPressedView)
        
        self.sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
            if node.name == "robo" {
                switch recognizer.state {
                case .began:
                    localTranslatePosition = touch
                case .changed:
                    let deltaX = Float(touch.x - self.localTranslatePosition.x)/700
                    let deltaY = Float(touch.y - self.localTranslatePosition.y)/700
                    
                    node.localTranslate(by: SCNVector3(deltaX,0.0,deltaY))
                    self.localTranslatePosition = touch
                default:
                    break
                }
            }
        }
    }
}

extension GestureViewController: ARSCNViewDelegate {
    
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        if anchor is ARPlaneAnchor {
            DispatchQueue.main.async {
                self.mBProgressHUD.label.text = "平面検出OK"
                self.mBProgressHUD.hide(animated: true, afterDelay: 0.5)
            }
            
        }
    }
}

スポンサーリンク

Posted by nobuhiro harada