SceneKitで全天球画像ビューアを作ろう

#この記事の概要
SceneKitとCoreMotionを使ってiOSで全天球画像のビューアを作ろう

#出来上がるもの
<blockquote class="twitter-video" data-lang="ja"><p lang="ja" dir="ltr">全天球カメラビューア <a href="https://t.co/LBjIeMrCk5">pic.twitter.com/LBjIeMrCk5</a></p>&mdash; noppe (@noppefoxwolf) <a href="https://twitter.com/noppefoxwolf/status/717014686269444096">2016年4月4日</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

#カメラと照明と球体を用意する

<img width="731" alt="Screen Shot 2016-04-05 at 13.17.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/24979/c86aad7b-0204-218e-c8e3-1216bdd1c7e6.png">

新規プロジェクトからGameを選択します。

まずはカメラと全天球の珠を置いてみましょう。

<img width="578" alt="Screen Shot 2016-04-05 at 13.19.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/24979/7de0ff2f-dee8-89ac-1463-567b4b2363fd.png">

イメージとしては、このようにカメラを球体の中に入れて球体の内側に画像を表示することで全天球を表示します。

まずはシーンを作成します。このシーンにカメラや球体を置くことでシーンを実装していきます。
シーンは言わばレゴの

![baseplates.jpg](https://qiita-image-store.s3.amazonaws.com/0/24979/342ea81f-31e2-1902-f3b3-1f25bd551f66.jpeg)

みたいなものです。

```swift

    let scene = SCNScene()
    guard let scnView = self.view as? SCNView else { return }
    scnView.scene = scene

```

下のスクリーンキャプチャから分かるように、このViewControllerのviewはSCNViewとなっています。
<img width="1588" alt="Screen Shot 2016-04-05 at 09.58.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/24979/a5a36065-1c8c-f245-10d6-117436a57fef.png">

通常はUIViewControllerのviewはUIViewですから、一度as?でSCNViewか確認してSCNViewのインスタントとして扱っていきます。
SCNViewのsceneプロパティにSCNSceneを入れることで、sceneをviewに表示します。


次にカメラを用意します。
SceneKitでは全てがSCNNodeで表されるので、SCNNodeを作りそこにカメラを与えます。

```swift

    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.position = SCNVector3Zero
    scene.rootNode.addChildNode(cameraNode)
```


次に環境光を設定します。
無いと真っ暗になります。

```swift
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light?.type = SCNLightTypeAmbient
    ambientLightNode.light?.color = UIColor.whiteColor()
    scene.rootNode.addChildNode(ambientLightNode)
```

これでカメラと環境光が設置出来たので、球体を作り中にカメラを入れます。

```swift
    let sphereGeometry = SCNSphere(radius: 20)
    let material = SCNMaterial()
    material.doubleSided = true
    material.diffuse.contents = UIImage(named: "tex")
    sphereGeometry.firstMaterial = material
    let boxNode = SCNNode(geometry: sphereGeometry)
    scene.rootNode.addChildNode(boxNode)
```

SCNSphareは球体のノードを生成します。
SCNMaterialでテクスチャを作ります。
doubleSidedをtrueにすることで表裏にテクスチャを貼ることが出来ます。（内側からテクスチャを見ることが出来る）

![Screen Shot 2016-04-06 at 22.36.38.png](https://qiita-image-store.s3.amazonaws.com/0/24979/00196918-6b8f-020a-a6d7-36ea619c8268.png)

この時点で起動するとこのように内側のテクスチャが映ります。


#端末の動きに合わせてカメラを動かす

```swift

    guard let queue = NSOperationQueue.currentQueue() else { return }
    manager.startDeviceMotionUpdatesToQueue(queue) { (motion, error) in
      guard let motion = motion else { return }
      cameraNode.orientation = self.orientationFromCMQuaternion(motion.attitude.quaternion)
    }
```

```swift
  private func orientationFromCMQuaternion(q: CMQuaternion) -> SCNVector4 {
    let gq1 = GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(-90), 1, 0, 0)
    let gq2 = GLKQuaternionMake(Float(q.x), Float(q.y), Float(q.z), Float(q.w))
    let qp  = GLKQuaternionMultiply(gq1, gq2)
    let rq  = CMQuaternion(x: Double(qp.x), y: Double(qp.y), z: Double(qp.z), w: Double(qp.w))
    return SCNVector4Make(Float(rq.x), Float(rq.y), Float(rq.z), Float(rq.w))
  }

```

iPhoneは画面を上に置いた時の方向を０としているので`orientationFromCMQuaternion`で縦に立てた時を０として補正しています。


これで端末の傾きと合わせて全天球画像を見ることが出来ました。

画像は
http://www.naoj.org/Gallery/j_360deg.html
より