Swift2で3Dモデルを描画してみる

#プレビュー
![IMG_0759.PNG](https://qiita-image-store.s3.amazonaws.com/0/55077/f102218d-64f0-9528-d5bc-5dd4df51bd2b.png)

#ソースコード
下記のChapter4を参考に
Swift2 iOS版に書き直しました
http://scenekitbook.com/sampleCode.html

SceneKitを利用して３Dモデルの描画をしています。
背景はカメラの画像です。

```ViewController.swift
import UIKit
import SceneKit
import AVFoundation

class ViewController: UIViewController {

    @IBOutlet weak var sceneView: SCNView!
    var ShouldUseVideoFromDevicesCamera = true; // Change to YES to enable video 
    var mySession : AVCaptureSession!
    var myDevice : AVCaptureDevice!
    var myImageOutput : AVCaptureStillImageOutput!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        sceneView.autoenablesDefaultLighting = true
        sceneView.allowsCameraControl = true
        sceneView.backgroundColor = UIColor.clearColor()

        // 動的にシーンを読み込む場合
        //sceneView.scene = SCNScene(named: "Spaceship.dae")

        // Do any additional setup after loading the view, typically from a nib.
        
        // Insert code here to initialize your application
        let spaceship = self.sceneView.scene?.rootNode.childNodeWithName("Spaceship", recursively: true)
        
        
        let shipMaterial = SCNMaterial()
        
        // UIImage objects
        shipMaterial.diffuse.contents  = NSBundle.mainBundle().pathForResource("diffuse", ofType: "png")
        shipMaterial.specular.contents = NSBundle.mainBundle().pathForResource("specular", ofType: "png")
        
        // CGImageRefs
        let normalImageURL = NSBundle.mainBundle().URLForResource("normal", withExtension: "png")
        let imageData = NSData(contentsOfURL: normalImageURL!)
        let imageDataProvider = CGDataProviderCreateWithCFData(imageData)
        let normalCGImage = CGImageCreateWithPNGDataProvider(imageDataProvider, nil, true,CGColorRenderingIntent.RenderingIntentDefault);
        
        shipMaterial.normal.contents = normalCGImage;
        
        
        shipMaterial.emission.contents = NSBundle.mainBundle().pathForResource("emission", ofType: "png")
        
        shipMaterial.multiply.contents = NSBundle.mainBundle().pathForResource("multiply", ofType: "png")
        
        
        
        // Layers
        if (ShouldUseVideoFromDevicesCamera) {
            videoPreviewLayer()
            // 描画順番を変える
            self.view.addSubview(sceneView)
           
        }
        
        
        spaceship!.geometry!.materials = [shipMaterial];

        /*
        // シーン内のカメラオブジェクトを取得
        let nodCamera = self.sceneView.scene?.rootNode.childNodeWithName("Camera", recursively: true)
        nodCamera!.position = SCNVector3(x: 0, y: 0, z: 300)
        //nodCamera!.rotation = SCNVector4(x:0,y:0,z:0,w:0)
        self.sceneView.scene?.rootNode.addChildNode(nodCamera!)
        */
    }
    

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    
    func videoPreviewLayer() -> CALayer? {
        
        // セッションの作成.
        mySession = AVCaptureSession()
        
        // デバイス一覧の取得.
        let devices = AVCaptureDevice.devices()
        
        // バックカメラをmyDeviceに格納.
        for device in devices{
            if(device.position == AVCaptureDevicePosition.Back){
                myDevice = device as! AVCaptureDevice
            }
        }
        
        do {
            // バックカメラからVideoInputを取得.
            let videoInput = try AVCaptureDeviceInput(device: myDevice)
            
            // セッションに追加.
            mySession.addInput(videoInput)
            
            // 出力先を生成.
            myImageOutput = AVCaptureStillImageOutput()
            
            // セッションに追加.
            mySession.addOutput(myImageOutput)
            
            // 画像を表示するレイヤーを生成.
            let myVideoLayer = AVCaptureVideoPreviewLayer(session:mySession)
            myVideoLayer.frame = self.view.bounds
            myVideoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
            
            // Viewに追加.
            self.view.layer.addSublayer(myVideoLayer)
            
            // セッション開始.
            mySession.startRunning()
            
            return myVideoLayer
        
        }catch{
            return nil
        }
        
        
    }
    
    override func shouldAutorotate() -> Bool{
        return false
    }
    
}
```

#リソース
リソースは下記Chapter4のものをお借りしました。
https://github.com/d-ronnqvist/SCNBook-code/tree/master/Chapter%2004%20-%20Adding%20Detail%20with%20Images/Assets

プロジェクトに下記リソースを追加してください
・Spaceship.dae:COLLADA 3Dモデルファイル(.dae)
・diffuse.png:カラーマップ
・normal.png:法線マップ（凸凹表現用）
・specular.png:スペキュラーマップ（金属の光沢表現用）
・emission.png:発光マップ（機体のライト部分を表現）
・multiply.png：乗算マップ（影の追加表現用）
![スクリーンショット 2016-02-14 18.15.50.png](https://qiita-image-store.s3.amazonaws.com/0/55077/7628883d-da9a-a2b8-026f-5dc634ea6f6c.png)

#ストーリーボード
SceneViewを一つ配置します。
Sceneには読み込むシーンであるSpaceship.daeを指定します。
![スクリーンショット 2016-02-14 18.24.30.png](https://qiita-image-store.s3.amazonaws.com/0/55077/12d15ca9-6599-f33c-19d4-bb26f33e38ee.png)


Referencing OutletsにSCNViewを紐付けします。
![スクリーンショット 2016-02-14 18.24.37.png](https://qiita-image-store.s3.amazonaws.com/0/55077/22b420ec-a631-7510-b9af-44981c89b0c8.png)
![スクリーンショット 2016-02-14 18.25.15.png](https://qiita-image-store.s3.amazonaws.com/0/55077/44479708-d9d8-2277-952c-52e3e7a5fd18.png)

#カメラ背景合成について
シーンビューの背景を透過して
カメラ背景をviewのaddSubLayerに追加
その後、SCNViewが手前になるように描画順番を変更しています。

```
// シーンビューの背景透過
sceneView.backgroundColor = UIColor.clearColor()
// カメラレイヤーをメインViewに追加
self.view.layer.addSublayer(myVideoLayer)
// シーンビューを手前に
self.view.addSubview(sceneView)            
```

#シーン内カメラについて
COLLADAモデルファイルは複数の３Dモデルオブジェクトや
カメラのオブジェクトをまとめてシーンとして保存できる３Dモデルフォーマットです。
XML形式で保存されています。

詳しいモデルフォーマットに関しては公式を参考にしてください
https://www.khronos.org/files/collada_spec_1_5.pdf

カメラの初期設定を変えるためにCOLLADAモデルを開いてみます。
![スクリーンショット 2016-02-14 22.24.31.png](https://qiita-image-store.s3.amazonaws.com/0/55077/f814a7e0-5d53-e297-6b8d-454c9cf9c10c.png)

カメラの見える範囲、視野角や奥行きの設定（射影変換行列）は下記に該当します。
![スクリーンショット 2016-02-14 22.29.48.png](https://qiita-image-store.s3.amazonaws.com/0/55077/b8b0903b-3db6-47d4-7414-4ee3f6ed84f2.png)

カメラの初期視点の設定は以下に該当します。（ビュー変換行列）
![スクリーンショット 2016-02-14 22.25.00.png](https://qiita-image-store.s3.amazonaws.com/0/55077/53381a60-fde1-3688-7ec0-e7ce1896703e.png)

上記でCameraがname="Camera"で保存されていることがわかりました。
下記のようにCameraオブジェクトを取得し、プログラムでカメラ位置を変更することもできます。

```
// シーン内のカメラオブジェクトを取得
let nodCamera = self.sceneView.scene?.rootNode.childNodeWithName("Camera", recursively: true)
nodCamera!.position = SCNVector3(x: 0, y: 0, z: 300)
self.sceneView.scene?.rootNode.addChildNode(nodCamera!)
```

また、SCNViewのallowsCameraControlプロパティをtrueにすることでタッチのピンチやスワイプなどでカメラを動かすことが可能になります。
（逆にユーザ操作で動かしたくない場合はfalseにします）

```
sceneView.allowsCameraControl = true
```