LoginSignup
0
1

3次元物体の回転~javascript~

Posted at

前回の2次元図形回転の復習

2次元図形回転はこちらで実装しています。なかなか好評?いいね2つとストック3つ頂いたので、調子乗って3次元物体の回転も解説したいと思います。
復習がてら2次元の場合の回転行列は下になります。

R(θ) =
\begin{pmatrix}
 \cosθ & -\sinθ \\
 \sinθ & \cosθ 
\end{pmatrix}

軸を中心に回転させる

2次元図形の回転はxy平面上の動きですが、3次元的に物体を回転させるには下のような動きをします。

Screenshot 2023-08-29 at 21.52.48.png

この例では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次元回転行列で実現できそうであるが、遠近感が得られず奥行きが感じられない。
回転1.gif
透視投影では、遠くのもの(ここでは奥行き方向の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);

回転2.gif

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1