Blender で作成したアニメーション付きモデルを ARKit で配置して遊ぼう - ARKit編

Blender で作成したアニメーション付きモデルを ARKit で配置する方法を解説していきます。
今回は ARKit で実際にアプリで表示できるようにしてみましょう。

前回の記事は以下です。

https://qiita.com/GleamingCake/items/75c50360015936c9a22e

## アニメーションの名前をチェックする

まずは作成したアニメーションの名前を知る必要があるので、作成したdaeファイルをテキストエディタで開いてください。下図の矢印が指しているところがアニメーションの名前となります。
おそらく `unnamed_animation__1` と命名されていると思います。名前をコピーしたらテキストエディタは閉じても構いません。
（なぜか Blender 側でアニメーションの名前を設定していても `unnamed_animation__1` になってしまいます...）
<img width="937" alt="11" src="https://user-images.githubusercontent.com/51015143/79456128-ece99680-8028-11ea-8823-b7df1d9d1665.png">

## Xcodeにデータを取り込む
File > New > File... から、SceneKit Catalog を作成します。これが 3Dデータのアセットを保存する場所になります。
そして、作成した art.scnassets の中に daeファイルをドラッグ&ドロップしてください。
<img width="1440" alt="12" src="https://user-images.githubusercontent.com/51015143/79456131-ee1ac380-8028-11ea-85da-0423fba90b59.png">

## ARKit で実装

UIKit の資産を使うので、`UIViewControllerRepresentable` でラップしてあげます。
あとは `ARSCNView` を使って実装していきましょう。
アニメーションは別に呼び出して `addAnimation` で追加してあげないと動かないので注意です。

```swift

import SwiftUI
import RealityKit
import ARKit

// SwiftUI 用の UIViewController ラッパー
struct ARViewControllerContainer: UIViewControllerRepresentable {
  typealias UIViewControllerType = ARViewController
  let arViewController: ARViewController
  
  init() {
    arViewController = ARViewController()
  }
  
  func makeUIViewController(context: Context) -> ARViewController {
    return arViewController
  }
  
  func updateUIViewController(_ arViewController: ARViewController, context: Context) {}
}

class ARViewController: UIViewController, ARSCNViewDelegate {
  let modelName= "Yaguchi_Panic" // モデルの名前
  let animationName = "unnamed_animation__1" // アニメーションの名前
  var sceneView = ARSCNView()
  let configuration = ARWorldTrackingConfiguration()
  var animations = [String: CAAnimation]()
  var idle: Bool = true
  var n = 0
  var modelNode = [SCNNode()]
  var isDetectPlane = false
  var frameNode: SCNNode!
  var planeNode: SCNNode? = nil
  var isShowingAnnotation = false
  var size: Float = 0.05
  
  // 画面ロード時に sceneView の設定をする
  override func viewDidLoad() {
    super.viewDidLoad()
    self.configuration.planeDetection = .horizontal
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
    self.sceneView.addGestureRecognizer(tapGestureRecognizer)
    sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
    self.view = sceneView
  }
  
  // 画面表示時にセッションを開始する
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.sceneView.session.run(configuration)
    self.sceneView.autoenablesDefaultLighting = true
  }
  
  // 画面非表示時にセッションを停止する
  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    sceneView.session.pause()
  }
  
  // 画面タップ時の動作
  @objc func tapped(sender: UITapGestureRecognizer) {
    // タップされた位置を取得する
    let tapLocation = sender.location(in: sceneView)
    
    // タップされた位置のARアンカーを探す
    guard let raycast = sceneView.raycastQuery(from: tapLocation, allowing: .estimatedPlane, alignment: .any) else { return }
    guard let result = sceneView.session.raycast(raycast).first else { return }
    self.addItem(hitTestResult: result)
  }
  
  /// アイテム配置メソッド
  private func addItem(hitTestResult: ARRaycastResult) {
    sceneView.debugOptions = []
    
    // 現実世界の座標を取得
    let transform = hitTestResult.worldTransform
    let thirdColumn = transform.columns.3
    
    // モデルをロードしてシーンにノードを追加
    let heroScene = SCNScene(named: "art.scnassets/\(modelName).dae")
    for childNode in heroScene!.rootNode.childNodes {
      modelNode[n].addChildNode(childNode)
    }
    
    // モデルからシーンのソースを作成
    let sceneURL = Bundle.main.url(forResource: "art.scnassets/\(modelName)", withExtension: "dae")
    let sceneSource = SCNSceneSource(url: sceneURL!, options: nil)
    
    // モデルのシーンからアニメーションを呼び出す
    if let animationObject = sceneSource?.entryWithIdentifier("\(animationName)", withClass: CAAnimation.self) {
      animationObject.repeatCount = 0
      // モデルにアニメーションを追加
      modelNode[n].addAnimation(animationObject, forKey: nil)
    }
    
    // モデルのサイズを設定
    modelNode[n].scale = SCNVector3Make(size, size, size)
    
    // モデルの向きをカメラ方向に設定
    if let camera = sceneView.pointOfView {
      modelNode[n].eulerAngles.y = camera.eulerAngles.y
    }
    
    // アイテムの配置
    modelNode[n].position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z)
    
    // シーンビューに追加する
    sceneView.scene.rootNode.addChildNode(modelNode[n])
    n += 1
    
    // ノードを追加する
    modelNode.append(SCNNode())
  }
  
  func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    print("renderer: didAdd")
  }
}
```