【ARKit】配置した3Dモデル(アニメーションつき)を削除する


はじめに

別記事で書いたアニメーションつきの3DモデルをARKitに追加するのに引き続き、ARKItに追加した3Dモデル(アニメーションつき)を削除するときにも躓いたので、そのメモ。

以下のサイトを参考に、長押しで3Dモデルを消そうとしたが削除できませんでした。このサイトではプリミティブなオブジェクト(cube)を配置しているので、今回使う3Dモデル(アニメーションつき)と何が違うのか?

実行環境

  • Xcode 11.2.1

3DモデルはMagicaVoxelで作って、Mixamo でアニメーションをつけたものを使用しました。

.scnファイルの構造

.scnファイル(sitting.scn)を開いてみると、直下に「sitting」ノードがあって、その中に3Dモデルとボーンの設定が含まれています。

アニメーション付き3Dモデルの配置時

配置するスクリプトは、直下の「sitting」のノードを取得して、self.mainSceneView.scene.rootNode に追加するようになっていますが、追加するノードに名前がないため、名前をつける処理(node.name = selectedItem)を追加しました。

※削除する際に、どのノードか判断するためです。

アニメーション付き3Dモデルの配置

    var selectedItem: String? = "sitting"

    //(中略)

    /// アイテム配置メソッド
    func addItem(hitTestResult: ARHitTestResult) {
        if let selectedItem = self.selectedItem {

            // .scnファイルから新しい3Dモデルのノードを作成
            let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn")
            let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))!

            // 現実世界の座標を取得
            let transform = hitTestResult.worldTransform
            let thirdColumn = transform.columns.3

            // 3Dモデルの配置
            node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)

            // 3Dモデルのサイズを変更
            node.scale = SCNVector3(0.05, 0.05, 0.05)

            // 3Dモデルに名前をつける
            node.name = selectedItem

            // シーンに追加
            self.mainSceneView.scene.rootNode.addChildNode(node)
        }
    }

アニメーション付き3Dモデルの削除

.scnファイルの構造で見たように、配置したモデルは「sitting」というノード名で、その中に3Dモデル(unamed)とボーン(mixamorig_Hips)が含まれています。

長押しでのオブジェクトが存在するかどうかの判定は、ARSCNView.hitTest(_:types:)で行いますが、このときの検出対象は3Dモデル(unamed)になるため、ノード全体を削除するには親である「sitting」ノードを削除する必要があります。

削除の処理は以下のようになります。

アニメーション付き3Dモデルの削除

    // ロングプレスイベントハンドラの登録
     let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressView))
     self.mainSceneView.addGestureRecognizer(longPressGesture)

    //(中略)

    // 長押しでキャラクタを削除する
    @objc func longPressView(sender: UILongPressGestureRecognizer) {
        print("----長押し!")

        if sender.state == .began {
            let location = sender.location(in: self.mainSceneView)
            let hitTest  = self.mainSceneView.hitTest(location)

            if let result = hitTest.first  {

                // 3Dアニメーションモデルは、複数パーツで構成されるため、親ノードの名前で判定・削除する
                if result.node.parent!.name == selectedItem
                {
                    result.node.parent!.removeFromParentNode();
                }
            }
        }
    }

まとめ

ノードを扱う際には、常に階層構造を意識しないとダメってことですね(わかってみれば当然ですが)。

他にもっといい方法があれば、教えてください。