RICOH THETA 専用カスタムバインディングを作った話

## RICOH THETA?

これです [RICOH THETA m15](http://www.ricoh-imaging.co.jp/japan/products/theta_m15/)
![ricoh_theta_m15.jpg](https://qiita-image-store.s3.amazonaws.com/0/25145/d6b6cc19-b138-43ca-a3b5-de2e606eb7fd.jpeg)

なにかというと、ワンタッチで Google ストリートビューみたいな全天球写真 360° x 360° を撮影できるカメラなんです。真ん中にあるボタンでも撮影できますが、スマートフォンと Wifi 接続（THETA が親機になる）することでスマートフォンがシャッターになります。

ちょうどベトナムに旅行に行く機会があったので、すぐに買って持っていきました。
で、ツアーの方々と一緒に THETA を囲って撮影したりなんてことがあったのですが、撮影した写真は適切に処理してあげないと**グニュァアア**っと歪んでいて見られたものではありません。

**どうやって共有したものか** と悩んでいたところ [three.js を使って全天球処理する話](http://blog.mitsuruog.info/2013/12/ricoh-theta360threejsfileapidrag.html)があったので、これを Knockout のカスタムバインディングにして独自ビューアを作ってみました。

## カスタムバインディング theta

何はともあれ [DEMO](http://sukobuto.com/files/knockout.theta/) をご覧ください。

> Github: [knockout.theta](https://github.com/sukobuto/knockout.theta)
jQuery と Three.js に依存してるので、先に読み込む必要があります。

カスタムバインディング自体はかなり単純で、

- 指定された URL の全天球画像を 3D 表示する
- マウスドラッグで視点追従する
- マウスホイールで拡大縮小

の3つの機能しかありません。
KnockoutJS ですので、とことんシンプルにして View と ViewModel で独自に機能を追加できるように意識しました。

### コード例1

撮影した全天球画像ファイルの URL をバインドするだけで 3D 表示されます。
この状態でマウスドラッグ/マウスホイールの操作に対応します。

```html
<div data-bind="theta: 'path/to/image.jpg'"></div>
```

これだけなら、ko.applyBindings({}) だけで動くのでお手軽です。
ただ、div タグの領域サイズは自動で設定されないので、CSS などで指定してください。
DEMO では Twitter Bootstrap の [Responsive embed](http://getbootstrap.com/components/#responsive-embed) で16:9にしています。

### コード例2 : Observableで変更を反映

もちろん Observable にすれば簡単に写真を変更できます。

```html
<div data-bind="theta: imageUrl"></div>
```

```js
function ThetaViewModel() {
    var self = this;
    self.imageUrl = ko.observable('path/to/image.jpg');

    // ... その後
    self.imageUrl('path/to/other_image.jpg'); // これで写真が切り替わる
}
ko.applyBindings(new ThetaViewModel());
```

### コード例3: thetaWheelZoom ズーム機能の On/Off 切り替え

大きな領域でマウスホイールの挙動を奪ってしまうと、サイト全体をスクロールする際の使い勝手が非常に悪くなってしまいます。何も考えずに Google Map を張り付けたりすると同じ事が起きますよね。

これをハンドリングするために、ホイールズームの有効/無効を切り替えられるようにしました。

```html
<div data-bind="theta: imageUrl, thetaWheelZoom: zoomEnabled"></div>
```

```js
function ThetaViewModel() {
    var self = this;
    self.imageUrl = ko.observable('path/to/image.jpg');
    self.zoomEnabled = ko.observable(true);

    // ... その後
    self.zoomEnabled(false); // これでズームできなくなる
}
ko.applyBindings(new ThetaViewModel());
```

DEMO ではこれを使ってカーソルが真ん中にあるときにしかズームできないように細工してあります。

### コード例4: thetaLoading 写真読み込み中を知る

theta で撮影した画像は 3584px x 1792px で、だいたい 2MB 以上あります。
キャッシュは効きますがこれを読み込むので、スタート時と変更時にどうしても待ち時間が発生します。

そこで、`thetaLoading `を使って読み込み中はローディングスピナーを表示するといいです。

```html
<div data-bind="theta: imageUrl, thetaLoading: isLoading"></div>

<p data-bind="visible: isLoading">Now loading...</p>
<p data-bind="visible: !isLoading()">Loading completed!</p>
```

```js
function ThetaViewModel() {
    var self = this;
    self.imageUrl = ko.observable('path/to/image.jpg');

    // 画像読み込み中は自動的に true になる
    self.isLoading = ko.observable();
}
ko.applyBindings(new ThetaViewModel());
```

> `thetaLoading` は View → ViewModel の OneWay バインディングですので、必ず `ko.observable` で宣言したプロパティをバインドしてください。[Knockout ES5](http://kojs.sukobuto.com/tips/knockout-es5) を使っている場合は `observable` が `ko.track` によって `getter/setter` にアップグレードされないように注意してください。

DEMO では [whirl](http://jh3y.github.io/whirl/) を使って読み込み中を表現しています。

## 何をつくろうか！

THETA で撮影した旅の思い出を記録するもよし、不動産屋さんや宿泊施設などのサイトで使うもよし、なにしろ MVVM で工夫し放題なのでよかったらみんなも THETA しましょう！

話は少し変わりますが、アプリのほうでは[SDKがあってDEVELOPPERS登録を受け付けている](http://qiita.com/hata_taka_820/items/1233ded3452e4da6cefc)ようです。
実はこの THETA、動画も撮影できるので Occulus Rift なんかと組み合わせたりと色々な可能性がありそうです。

※私は RICOH さんとは何の関係もございません。ご了承ください。
※[Knockout日本語ドキュメント](http://kojs.sukobuto.com/)も進めます。お待たせして申し訳ございません。