SceneKitで扱える3Dモデルのフォーマット／アニメーションつき3DモデルをSceneKitで使う

やりたいこと：

- 各種モデリングソフトからエクスポートされた3Dモデルを、SceneKit (on iOS)で使いたい
- モデルに紐づくアニメーションとかボーン(Rigged Models)とかもそのまま使いたい

ちなみになぜUnityじゃないかというと、**非ゲームな、基本的には2D**のアプリで、**一部で「3D的表現」**を行いたい、という要件からです。


##扱えるフォーマット

<del>結論からいうと、`.dae`（Digital Asset Exchange / COLLADA）の**一択**</del>。（※追記: プロジェクトにリソースとして追加してあるdaeファイルは実際に使えますし、以下のコードで動作するのですが、「SceneKitが扱えるフォーマット」という点ではこの記述は語弊があります。詳しくはコメント欄をご参照ください）

こんな感じで使います。

```swift
let url = Bundle.main.url(forResource: "filename", withExtension: "dae")!
let sceneSource = SCNSceneSource(url: url, options: nil)!
guard let modelNode = sceneSource.entryWithIdentifier("modelId", withClass: SCNNode.self) else {
    fatalError()
}
scene.rootNode.addChildNode(modelNode)
    
if let animation = sceneSource.entryWithIdentifier("animationId", withClass
: CAAnimation.self) {
    modelNode.addAnimation(animation, forKey: "someKey")
}
```

他のフォーマットがNGだった理由は以下の通り。

- `.fbx`は配布されているSDKを使うことでインポートできるが、**頻繁にメンテされてるわけでもなさそう**なので、そこに依存したくない
- `.obj`はSCNSceneSourceインポートできるが、そのファイル形式自体が**アニメーションをサポートしていない**
- `.3ds`, `.max` はSceneKitにインポートできない（サードパーティ製ライブラリとかはあるかもしれない）

そしてこのCOLLADAの.daeファイルは大抵のモデリングツールでサポートされているらしい。


##モデルの構造が複雑な場合

複雑というか、むしろ大抵のモデルは複数のオブジェクトやボーンから構成されている。

例えば、Appleの[Scene Kit Animations](https://developer.apple.com/library/content/samplecode/SceneKitAnimations/Introduction/Intro.html)という
フリーのこのモデルの.daeファイルをXcodeで見てみると、こんな感じでメッシュやらボーンやらでモデルが構築されています。

![Screen Shot 2017-01-12 at 12.50.13.png](https://qiita-image-store.s3.amazonaws.com/0/3180/b9590491-206f-1075-49ce-920f933e8f88.png)

上でやったように、SCNSceneSourceでIDを指定してSCNNodeをつくってはaddChildする、という方法はこういうケースでは得策ではありません。

ではどうするかというと、非常にシンプルで、**`dae` から SCNScene をつくる**だけ。

```swift
let url = Bundle.main.url(forResource: filename, withExtension: "dae")!
let modelScene: SCNScene
do {
    modelScene = try SCNScene(url: url, options: nil)
} catch {
    fatalError()
}
```

この`rootNode`を別のシーンに`addChild`しようとしたら "removing the root node of a scene from its scene is not allowed" って怒られてしまったので、以下のように新しいノードに付け替えました。

```swift
let modelNode = SCNNode()
for childNode in modelScene.rootNode.childNodes {
    modelNode.addChildNode(childNode)
}
```

![applemodel.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/14049896-b475-8e3f-1ce0-05148d45aefb.gif)


##テクスチャが読み込めない？

[Animated Army Pilot]( http://tf3dm.com/3d-model/army-pilot-fully-rigged-animated-20-animations-57044.html)というモデルで同様の手法を試したところ、ジオメトリとアニメーションは再現されるものの、テクスチャが適用されませんでした。（※テクスチャはちゃんとプロジェクトに追加してありました）

実行時にこういうエラーが出ていました。

> [SceneKit] Error: Failed loading : <C3DImage src:file:///Users/shuichi/Library/Developer/CoreSimulator/Devices/B2AF078C-BF73-429A-B9C0-182717D92859/data/Containers/Bundle/Application/55163269-393C-4F75-B729-5AF746CFDCA7/SceneKitModel.app/ [0.000000x0.000000]

ん？パスがちょっとおかしい。テクスチャが見つからないならこういうエラーが出るはず。

> [SceneKit] Error: Failed loading : <C3DImage src:file:///Users/shuichi/Library/Developer/CoreSimulator/Devices/B2AF078C-BF73-429A-B9C0-182717D92859/data/Containers/Bundle/Application/55163269-393C-4F75-B729-5AF746CFDCA7/SceneKitModel.app/male_large_01.png

`.dae`の中身を見てみると（**XMLファイルなので普通に見れる**）、

```xml
  <library_images>
    <image id="head01-image" name="head01"><init_from>file://C:\Users\Nathan\Documents\New Project\Assets\Textures\soldier\head01.png</init_from></image>
    <image id="body01-image" name="body01"><init_from>file://C:\Users\Nathan\Documents\New Project\Assets\Textures\soldier\body01.png</init_from></image>
    <image id="m4tex-image" name="m4tex"><init_from>file://C:\Users\Nathan\Documents\New Project\Assets\Textures\soldier\m4tex.png</init_from></image>
  </library_images>
```

なんか制作環境の絶対パスが入っています。。

大丈夫なのか、と思いましたが、うまくいっているAppleの方のモデルの中身を見てみると、

```xml
  <library_images>
    <image id="male_large_01_png">
      <init_from>file:///Z:/art_depot/quest/characters/male_large_01/male_large_01.png</init_from>
    </image>
  </library_images>
```

こちらも絶対パスになっていました。

じゃあパスの指定方法が間違ってるのか、と思い、以下のように直してみました。

```xml
  <library_images>
    <image id="head01-image" name="head01"><init_from>file///C:/Users/Nathan/Documents/New Project/Assets/Textures/soldier/head01.png</init_from></image>
    <image id="body01-image" name="body01"><init_from>file///C:/Users/Nathan/Documents/New Project/Assets/Textures/soldier/body01.png</init_from></image>
    <image id="m4tex-image" name="m4tex"><init_from>file///C:/Users/Nathan/Documents/New Project/Assets/Textures/soldier/m4tex.png</init_from></image>
  </library_images>
```

`://` を `///` に、`\` を `/` に。

→ うまくいきました


![army.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/50f36764-c71a-cfe0-3aee-2d009db436ea.gif)

同じ仕様に則っているとはいえ、各社が別々にモデリングツールを開発していて、しかもメインとするプラットフォームが違ったりする（たとえば3DS MAXはWindows版しかない）中で、こういう**エクスポートしたファイルフォーマットの微妙な違いによるトラブル**というのは往々にしてあると思います。

上記のようなパスの違いが何によって生じたのか、よくあることなのかわかりませんが、ひとつの事例として。


## .dae ファイルを .scn ファイルに変換する手順

1. .daeファイルをXcodeプロジェクトに追加する
2. 1で追加したファイルを選択する（SceneKitエディタでモデルが表示される）
3. [Editor] > [Convert to scn file format] で変換される

- 参考： https://stackoverflow.com/questions/31627965/how-to-convert-dae-to-scn-files-in-scenekit