前回の2次元図形回転の復習
2次元図形回転はこちらで実装しています。なかなか好評?いいね2つとストック3つ頂いたので、調子乗って3次元物体の回転も解説したいと思います。
復習がてら2次元の場合の回転行列は下になります。
R(θ) =
\begin{pmatrix}
\cosθ & -\sinθ \\
\sinθ & \cosθ
\end{pmatrix}
軸を中心に回転させる
2次元図形の回転はxy平面上の動きですが、3次元的に物体を回転させるには下のような動きをします。
この例ではy軸を中心に回転させていますが、x軸z軸を中心に回転させることもできます。
\begin{pmatrix}
x' \\
y' \\
z'
\end{pmatrix}
=
\begin{pmatrix}
\cosθ && 0 && \sinθ \\
0 && 1 && 0 \\
-\sinθ && 0 && \cosθ
\end{pmatrix}
\begin{pmatrix}
x \\
y \\
z
\end{pmatrix}
x' = \cosθx + \sinθz
y' = y
z' = -\sinθx + \cosθz
以上の関係より回転後の座標y'はcosθ sinθに影響されず一定なので、y軸を中心に回転する。
ピンホールカメラ (透視投影)
上の3次元回転行列で実現できそうであるが、遠近感が得られず奥行きが感じられない。
透視投影では、遠くのもの(ここでは奥行き方向のz値が大きいもの)が近くのもの(z値が小さいもの)より小さく描くようにし遠近感を得る。
x' = \frac{x}{z}
y' = \frac{y}{z}
上の式をコードに組みこみ回転させてみた結果を見てみましょう!
実際のプログラム
以下のコードで実装できます!
3次元の回転
const canvas = document.getElementById("target");
const context = canvas.getContext("2d"); //2次元描画
let degree = 0;
function triangle(fig,x1,y1,x2,y2,x3,y3,s){
if (s==true){
fig.beginPath(); //パスの作成
fig.moveTo(x1,y1);
fig.lineTo(x2,y2);
fig.lineTo(x3,y3);
fig.closePath();
fig.fillStyle = "blue";
fig.fill();
}else{
fig.beginPath();
fig.moveTo(x1,y1);
fig.lineTo(x2,y2);
fig.lineTo(x3,y3);
fig.closePath();
fig.fillStyle = "black";
fig.fill();
}
}
// 三角形の重心 (3次元)
let center = (A) => [
(A[0][0] + A[1][0] + A[2][0]) / 3,
(A[0][1] + A[1][1] + A[2][1]) / 3,
(A[0][2] + A[1][2] + A[2][2]) / 3
];
function draw(){
context.clearRect(0,0,canvas.width,canvas.height);
let V = [
[0, 0, 0],
[1, 0, 0],
[0, 0, 1],
[0, -1, 0]
]
let F = [
[0, 1, 2],
[0, 1, 3],
[1, 2, 3],
[0, 2, 3]
]
let width = canvas.width;
let height = canvas.height;
let S = V;
let c = center(S);
degree+=1
r=(degree*Math.PI)/180
let vx = new Array(4);
let vy = new Array(4);
let vz = new Array(4);
for(let i=0; i<4; i++){
vx[i] = S[i][0] - c[0];
vy[i] = S[i][1] - c[1];
vz[i] = S[i][2] - c[2];
// ベクトルを回転
S[i][0] = vx[i]*Math.cos(r) + vz[i]*Math.sin(r);
S[i][1] = vy[i];
S[i][2] = -vx[i]*Math.sin(r) + vz[i]*Math.cos(r);
for(let j=0; j<3; j++){
S[i][j]+=c[j];
}
}
// 並行移動
for(let i=0; i<4; i++){
S[i] = [S[i][0],S[i][1]+0.5,S[i][2]+2];
}
let T = S;
// ピンホールカメラ (透視投影)
for(let i=0; i<4; i++){
S[i] = [S[i][0]/S[i][2], S[i][1]/S[i][2]];
}
// 拡大 (画面の大きさに合わせて)
for(let i=0; i<4; i++){
S[i] = [S[i][0]/2*width+width/2, S[i][1]/2*height+height/2];
}
for(let i=0; i<F.length; i++){
triangle(context,S[F[i][0]][0],S[F[i][0]][1],S[F[i][1]][0],S[F[i][1]][1],S[F[i][2]][0],S[F[i][2]][1],true);
}
triangle(context,T[0][0],T[0][1]+150,T[1][0],T[1][1]+150,T[2][0],T[2][1]+150,false);
}
setInterval("draw()",20);