Orbit(軌道上)にあるようなカメラの視点移動を提供するものです。
Three.jsでも提供されています。
ちょっとしたモデルを色々な方向から見せたい、ということはよくあると思います。
今回はそのOrbitController風の視点移動をさせるコードを載せています。
サンプルコード
(function () {
'use strict';
function OrbitViewer(camera, target) {
this._camera = camera;
this._target = target;
this._init();
}
OrbitViewer.prototype = {
constructo: OrbitViewer,
_camera: null,
_target: null,
_prevX: 0,
_prevY: 0,
orbitX: 0,
orbitY: 0,
scale : 10,
maxRotationY: 90 * Math.PI / 180,
minRotationY: 0,
_init: function () {
document.body.addEventListener('mouseup', this._mouseup.bind(this), false);
document.body.addEventListener('mousedown', this._mousedown.bind(this), false);
document.body.addEventListener('mousewheel', this._mousewheel.bind(this), false);
},
_mousedown: function (e) {
this._prevX = e.pageX;
this._prevY = e.pageY;
if (!this._bindMousemove) {
this._bindMousemove = this._mousemove.bind(this);
}
document.body.addEventListener('mousemove', this._bindMousemove, false);
},
_mouseup: function (e) {
document.body.removeEventListener('mousemove', this._bindMousemove, false);
},
_mousewheel: function (e) {
this.scale -= e.wheelDelta / 5;
e.preventDefault();
},
_mousemove: function (e) {
var deltaX = e.pageX - this._prevX;
var deltaY = e.pageY - this._prevY;
this.orbitX -= deltaX / 1000;
this.orbitY += deltaY / 1000;
if (this.orbitY > this.maxRotationY) {
this.orbitY = this.maxRotationY;
}
else if (this.orbitY < this.minRotationY) {
this.orbitY = this.minRotationY;
}
this._prevX = e.pageX;
this._prevY = e.pageY;
},
update: function () {
// Yが0(sin(0))のときは1となり、地面からの角度は0
var x = Math.sin(this.orbitX) * Math.cos(this.orbitY) * this.scale;
// Yが0でXも0のときに1となる計算。
var z = Math.cos(this.orbitX) * Math.cos(this.orbitY) * this.scale;
// Yは単純にYの位置のsinで決まる
var y = Math.sin(this.orbitY) * this.scale;
this._camera.position.set(x, y, z);
this._camera.lookAt(this._target.position);
}
};
window.OrbitViewer = OrbitViewer;
}());
まぁなんだかんだやっていますが、update
メソッド以外は単純にマウスイベントを取ってドラッグ状態を取得しているだけです。
update
メソッドの部分が実際に軌道上にあるようなカメラの視点移動をしている部分です。
(ちなみに、Three.jsと一緒に使う想定でサンプルを作っているのでいくつかThree.jsで定義されているものを使っていますが、基本的にx, y, z
を適切に設定するようにすればどんな環境でも動きます)
update
メソッドの実装
update
メソッド内でなにやら色々やってます。
x, y, z
それぞれをsin
、cos
を使って計算しています。
(実はここをメモしておきたかっただけなんて言えない)
それぞれなにをしているか見て行きましょう。
(ちなみに球体上に点をプロットするのも同じ理屈です)
Y座標
一番シンプルなY座標から。
(this.scale
はたんに距離の調整用なので割愛します。大きい値になれば注視点とカメラの位置が離れます)
var y = Math.sin(this.orbitY);
ただのsin
ですねw
Y軸に関しては上下に移動するだけなのでMath.sin
にorbitY
(カメラ視点移動の計算用に保持している現在の位置)を入れているだけです。
sin
は-1〜1
の間を往復するので、それに距離であるscale
を掛けてあげればOKです。
(サンプルコードでは反転しないようにYの値の最小/最大値を設定しています)
X座標
var x = Math.sin(this.orbitX) * Math.cos(this.orbitY);
X座標についての計算です。
ここでの計算は「X軸の位置」なので、当然着目するのはX軸の値です。
上図では円上になっていますが、着目するのは実線で書いた線分の長さです。
-1〜1の範囲で上(下)にいくに連れて短くなっているのが分かります。
そしてこの長さの変化はYの値によります。
つまり、 X軸の値はYの位置(高さ)のcosによる比率 ということができます。
なので、Math.sin(this.orbitX) * Math.cos(this.orbitY)
となるわけです。
Z座標
var z = Math.cos(this.orbitX) * Math.cos(this.orbitY);
最後はZ座標です。
といっても考え方はX軸のときとほぼ同じです。
視点を90度上に持ってきて見れば、X, Yの座標系がX, Zの座標系に変わるだけですね。
ただひとつ違う点があって、X軸の計算の場合、orbitX
、orbitY
ともに0
の場合は0
でした。
つまり出発点が0
、ということです。
しかしZ軸の出発点は0
だと困ります。出発点は1
としたいですね。
答えは単純で、Math.cos(this.orbitX)
とすれば初期値が1
となります。(cos(0) = 1
)
あとは高さに依存するのはX軸と同様なので、X軸同様、Math.cos(this.orbitY)
を掛けてやればZ軸の値が求まります。
まとめ
以上でx, y, z
それぞれの値が計算できました。
あとは自由に半径などの補正をして、それをそのままカメラの位置として設定してあげれば軌道上の人工衛星から撮影するような感じで、対象物のまわりをぐるぐる回るカメラの視点移動が完成する、というわけです。
(本当はlookAt
的なこともやらないとなりませんが、位置の計算、ということで割愛。サンプルコードではThree.jsのlookAt
メソッドをそのまま使っています)