名前がよくわかりませんが、動作を見てもらえれば一発でわかると思います。
youtu.be
ドラッグによるカメラコントロールというのでしょうか?
やり方
方針
ドラッグ開始地点からの差分にもとづいてX軸、Y軸の回転を計算します。
このような感じです。asin(移動したぶん)
で角度が求まります。
回転行列について
CSS transform には matrix3d という関数があり、行列を渡すと回転させることができます。
行列の基本的な部分については既知として、回転行列から話していきます。
まず、なにも変形しないときは 4x4 の単位行列になります。
[
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
]
X軸まわりでの回転を表す回転行列はこのようになります。
[
[1, 0, 0, 0],
[0, cos(theta), -sin(theta), 0],
[0, sin(theta), cos(theta), 0],
[0, 0, 0, 1],
]
(Y軸、Z軸についてはwikiを参照ください。)
回転行列をかけてやると、回転後の行列が求まります。とても簡単ですね。
しかし、今回やりたいことはこれだけでは実現できません。なぜなら、回転させてやると軸も一緒に回転してしまいます。例えばY軸まわりに90度回転させると、もともとのX軸のところにはZ軸、Z軸のところにはX軸がきます。
そのため、1度目の回転はそれぞれの軸まわりでの回転行列をつかえば良いですが、2回目以降はみかけ上の3軸を計算し、それらの軸での回転行列を使う必要があります。
まず、みかけ上の3軸は簡単に求まります。単純にそれぞれの軸を回転させるだけです。
次に、それぞれの軸での回転行列ですが、これもwikiにあるロドリゲスの回転公式がそのまま使えます。
というわけで、道具はそろったので実際にプログラムにしてみます。
実際のプログラム
const rot = ([nx, ny, nz], theta) => { };
const multiple = (a, b) => { };
const transpose = (a) => { };
const state = {
prevMatrix: [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
],
theta: 0,
phi: 0,
xAxis: [1, 0, 0, 0],
yAxis: [0, 1, 0, 0],
get matrix() {
return multiple(
multiple(rot(this.xAxis, this.phi), rot(this.yAxis, this.theta)),
this.prevMatrix
);
},
};
let isDragging = false;
let initialX = 0,
initialY = 0;
const mouseup = () => {
const matrix = state.matrix.map((arr) => arr.concat());
isDragging = false;
for (let i = 0; i < 4; i++)
for (let j = 0; j < 4; j++) {
state.prevMatrix[i][j] = matrix[i][j];
}
const rotation = multiple(
rot(state.xAxis, state.phi),
rot(state.yAxis, state.theta)
),
transpose = (value) => value.reduce((acc, item) => [...acc, [item]], []);
Object.assign(state, {
xAxis: multiple(rotation, transpose([state.xAxis])).map(([value]) => value),
yAxis: multiple(rotation, transpose([state.yAxis])).map(([value]) => value),
phi: 0,
theta: 0,
});
};
const mousemove = ({
clientX,
clientY,
view: { innerWidth: width, innerHeight: height },
}) => {
if (!isDragging) {
return;
}
const wid = width / 2,
hei = height / 2;
Object.assign(state, {
theta: Math.asin((clientX - wid) / wid) - Math.asin((initialX - wid) / wid),
phi: Math.asin((clientY - hei) / hei) - Math.asin((initialY - hei) / hei),
});
};
window.addEventListener('mouseleave', mouseup);
window.addEventListener('mouseup', mouseup);
window.addEventListener('mousemove', mousemove);
window.addEventListener('mousedown', ({ clientX, clientY }) => {
isDragging = true;
initialX = clientX;
initialY = clientY;
});