LoginSignup
3

More than 1 year has passed since last update.

posted at

updated at

🦀Rust + 💻WebGL = 💛 2. 行列計算編

前回の記事ではWebGLRenderingContextの使い方を書きました。今回は、行列計算がテーマです。ですが、行列計算のライブラリを自作するのは控えめに言って面倒です。特に理由がなければ、nalgebrandarrayを使うことをお勧めします。私はレンダリングの仕組みが知りたかったので自作しました。

前提条件

  • 行列計算の基礎
  • 座標変換の基礎
  • Rustの基礎

model matrix

平行移動

\begin{bmatrix}
1 & 0 & 0 & x\\
0 & 1 & 0 & y\\
0 & 0 & 1 & z\\
0 & 0 & 0 & 1
\end{bmatrix}

拡大・縮小

\begin{bmatrix}
x & 0 & 0 & 0\\
0 & y & 0 & 0\\
0 & 0 & z & 0\\
0 & 0 & 0 & 1
\end{bmatrix}

x軸を中心に回転

\begin{bmatrix}
1 & 0 & 0 & 0\\
0 & \cos\theta & -\sin\theta & 0\\
0 & \sin\theta & \cos\theta & 0\\
0 & 0 & 0 & 1
\end{bmatrix}

y軸を中心に回転

\begin{bmatrix}
\cos\theta & 0 & \sin\theta & 0\\
0 & 1 & 0 & 0\\
-\sin\theta & 0 & \cos\theta & 0\\
0 & 0 & 0 & 1
\end{bmatrix}

z軸を中心に回転

\begin{bmatrix}
\cos\theta & -\sin\theta & 0 & 0\\
\sin\theta & \cos\theta & 0 & 0\\
0 & 0 & 1 & 0\\
0 & 0 & 0 & 1
\end{bmatrix}

設計思想・概要

と言っても大したことはないのですが……。メソッドチェーンで繋げられるように書きました。なので行列の掛け算も2つの行列を引数に取るのではなく、掛け算合わせる行列を指定し、どんどん繋げられるようにしました。WebGLで使うので列オーダーかつ右手座標系です。。
使い方に関してはソースコード内のコメントと使用例をご覧ください。

mat_4.rs
pub struct Matrix {
    value: [f32; 16],
}

#[allow(dead_code)]
impl Matrix {

    //--create new matrix--
    //  <return> Matrix
    pub fn new() -> Self {
        Self {
            value: [
                1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
            ] as [f32; 16],
        }
    }

    //--Get matrix value--
    //  <return> [f32; 16]
    pub fn get_value(&self) -> [f32; 16] {
        self.value
    }

    //--Set a value in the matrix
    //  <argument>
    //      m &[f32; 16] : value to set
    pub fn set_value(&mut self, m: &[f32; 16]) -> &mut Self {
        self.value = *m;
        self
    }

    //--Set the identity matrix in the matrix
    pub fn set_identity(&mut self) -> &mut Self {
        self.value = [
            1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
        ];
        self
    }

    //--Set other matrix value in the matrix
    //  <argument>
    //      m &Matrix : other matrix to set
    pub fn substitution(&mut self, m: &Matrix) -> &mut Self {
        self.value = m.value;
        self
    }

    //--Multiply other matrix
    //  <argument>
    //      m &Matrix : other matrix to multiply
    //  <note>
    //      Column-major order!
    pub fn multiply(&mut self, m: &Matrix) -> &mut Self {
        let mut dest: [f32; 16] = [0.; 16];

        dest[0] = m.value[0] * self.value[0]
            + m.value[1] * self.value[4]
            + m.value[2] * self.value[8]
            + m.value[3] * self.value[12];
        dest[1] = m.value[0] * self.value[1]
            + m.value[1] * self.value[5]
            + m.value[2] * self.value[9]
            + m.value[3] * self.value[13];
        dest[2] = m.value[0] * self.value[2]
            + m.value[1] * self.value[6]
            + m.value[2] * self.value[10]
            + m.value[3] * self.value[14];
        dest[3] = m.value[0] * self.value[3]
            + m.value[1] * self.value[7]
            + m.value[2] * self.value[11]
            + m.value[3] * self.value[15];

        dest[4] = m.value[4] * self.value[0]
            + m.value[5] * self.value[4]
            + m.value[6] * self.value[8]
            + m.value[7] * self.value[12];
        dest[5] = m.value[4] * self.value[1]
            + m.value[5] * self.value[5]
            + m.value[6] * self.value[9]
            + m.value[7] * self.value[13];
        dest[6] = m.value[4] * self.value[2]
            + m.value[5] * self.value[6]
            + m.value[6] * self.value[10]
            + m.value[7] * self.value[14];
        dest[7] = m.value[4] * self.value[3]
            + m.value[5] * self.value[7]
            + m.value[6] * self.value[11]
            + m.value[7] * self.value[15];

        dest[8] = m.value[8] * self.value[0]
            + m.value[9] * self.value[4]
            + m.value[10] * self.value[8]
            + m.value[11] * self.value[12];
        dest[9] = m.value[8] * self.value[1]
            + m.value[9] * self.value[5]
            + m.value[10] * self.value[9]
            + m.value[11] * self.value[13];
        dest[10] = m.value[8] * self.value[2]
            + m.value[9] * self.value[6]
            + m.value[10] * self.value[10]
            + m.value[11] * self.value[14];
        dest[11] = m.value[8] * self.value[3]
            + m.value[9] * self.value[7]
            + m.value[10] * self.value[11]
            + m.value[11] * self.value[15];

        dest[12] = m.value[12] * self.value[0]
            + m.value[13] * self.value[4]
            + m.value[14] * self.value[8]
            + m.value[15] * self.value[12];
        dest[13] = m.value[12] * self.value[1]
            + m.value[13] * self.value[5]
            + m.value[14] * self.value[9]
            + m.value[15] * self.value[13];
        dest[14] = m.value[12] * self.value[2]
            + m.value[13] * self.value[6]
            + m.value[14] * self.value[10]
            + m.value[15] * self.value[14];
        dest[15] = m.value[12] * self.value[3]
            + m.value[13] * self.value[7]
            + m.value[14] * self.value[11]
            + m.value[15] * self.value[15];

        self.value = dest;
        self
    }


    //--Transpose the matrix--
    pub fn transpose(&mut self) -> &mut Self {
        self.value = [
            self.value[0],
            self.value[4],
            self.value[8],
            self.value[12],
            self.value[1],
            self.value[5],
            self.value[9],
            self.value[13],
            self.value[2],
            self.value[6],
            self.value[10],
            self.value[14],
            self.value[3],
            self.value[7],
            self.value[11],
            self.value[15],
        ];

        self
    }

    //--Create translation matrix and multiply it--
    pub fn translation(&mut self, v: &[f32; 3]) -> &mut Self {
        let translation_mat = Matrix {
            value: [
                1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., v[0], v[1], v[2], 1.,
            ],
        };

        self.multiply(&translation_mat);
        self
    }

    //--Create scaling matrix and multiply it--
    pub fn scaling(&mut self, v: &[f32; 3]) -> &mut Self {
        let scaling_mat = Matrix {
            value: [
                v[0], 0., 0., 0., 0., v[1], 0., 0., 0., 0., v[2], 0., 0., 0., 0., 1.,
            ],
        };

        self.multiply(&scaling_mat);
        self
    }

    pub fn rotate_around_y(&mut self, rad: f32) -> &mut Self {
        let r_c = rad.cos();
        let r_s = rad.sin();
        let y_mut = Matrix {
            value: [
                r_c, 0., r_s, 0., 0., 1., 0., 0., -r_s, 0., r_c, 0., 0., 0., 0., 1.,
            ],
        };

        self.multiply(&y_mut);
        self
    }

    pub fn rotate_around_x(&mut self, rad: f32) -> &mut Self {
        let r_c = rad.cos();
        let r_s = rad.sin();
        let x_mut = Matrix {
            value: [
                r_c, 0., r_s, 0., 0., 1., 0., 0., -r_s, 0., r_c, 0., 0., 0., 0., 1.,
            ],
        };
        self.multiply(&x_mut);
        self
    }

    pub fn rotate_around_z(&mut self, rad: f32) -> &mut Self {
        let r_c = rad.cos();
        let r_s = rad.sin();
        let z_mut = Matrix {
            value: [
                r_c, -r_s, 0., 0., r_s, r_c, 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
            ],
        };
        self.multiply(&z_mut);
        self
    }

    //--Create look_at matrix--
    pub fn look_at(&mut self, from: &[f32; 3], to: &[f32; 3], up: &[f32; 3]) -> &mut Self {
        let mut l: f32;
        let mut x: [f32; 3] = [0.; 3];
        let mut y: [f32; 3] = [0.; 3];
        let mut z: [f32; 3] = [0.; 3];

        z[0] = from[0] - to[0];
        z[1] = from[1] - to[1];
        z[2] = from[2] - to[2];
        l = (z[0] * z[0] + z[1] * z[1] + z[2] * z[2]).sqrt().recip();
        z[0] *= l;
        z[1] *= l;
        z[2] *= l;

        x[0] = up[1] * z[2] - up[2] * z[1];
        x[1] = up[2] * z[0] - up[0] * z[2];
        x[2] = up[0] * z[1] - up[1] * z[0];
        l = (x[0] * x[0] + x[1] * x[1] + x[2] * x[2]).sqrt().recip();
        x[0] *= l;
        x[1] *= l;
        x[2] *= l;

        y[0] = z[1] * x[2] - z[2] * x[1];
        y[1] = z[2] * x[0] - z[0] * x[2];
        y[2] = z[0] * x[1] - z[1] * x[0];
        l = (y[0] * y[0] + y[1] * y[1] + y[2] * y[2]).sqrt().recip();
        y[0] *= l;
        y[1] *= l;
        y[2] *= l;

        let d_12: f32 = -(x[0] * from[0] + x[1] * from[1] + x[2] * from[2]);
        let d_13: f32 = -(y[0] * from[0] + y[1] * from[1] + y[2] * from[2]);
        let d_14: f32 = -(z[0] * from[0] + z[1] * from[1] + z[2] * from[2]);
        self.value = [
            x[0], y[0], z[0], 0.,
            x[1], y[1], z[1], 0.,
            x[2], y[2], z[2], 0.,
            d_12, d_13, d_14, 1.,
        ];
        self
    }

    //--Create perspective projections matrix--
    //  <argument>
    //      aspect f32  ratio parameter is the width divided by the height
    //      fovy   f32  field of view y-axis
    //      near   f32  near clipping plane
    //      far    f32  far clipping plane
    //  <note>
    //      Right-Handed Coordinate System!
    pub fn perspective(&mut self, aspect: f32, fovy: f32, near: f32, far: f32) -> &mut Self {
        let mut dest: [f32; 16] = [0.; 16];
        let t: f32 = (fovy / 2.).tan();
        let d: f32 = far - near;
        dest[0] = 1. / (t * aspect);
        dest[5] = 1. / t;
        dest[10] = - far / d;
        dest[11] = -1.;
        dest[14] = - far * near / d;

        self.value = dest;
        self
    }

    //--inverse the matrix
    pub fn inverse(&mut self) -> Result<&mut Self, i8> {
        const SIZE: usize = 4;
        let mut inv = Matrix::identity();
        let mut buf: f32;
        let mut a = self.value;

        for i in 0..SIZE {
            if a[i * SIZE + i] == 0. {
                return Err(-1);
            }
            buf = 1. / a[i * SIZE + i];
            for j in 0..SIZE {
                a[i * SIZE + j] *= buf;
                inv[i * SIZE + j] *= buf;
            }
            for j in 0..SIZE {
                if i != j {
                    buf = a[j * SIZE + i];
                    for k in 0..SIZE {
                        a[j * SIZE + k] -= a[i * SIZE + k] * buf;
                        inv[j * SIZE + k] -= inv[i * SIZE + k] * buf;
                    }
                }
            }
        }

        self.value = inv;
        Ok(self)
    }

    fn identity() -> [f32; 16] {
        [
            1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
        ] as [f32; 16]
    }
}

使用例1. MVP行列の作成

lib.rs
//初期化
let width = 500.;
let height = 500.;
let mut m_matrix = mat_4::Matrix::new();
let mut v_matrix = mat_4::Matrix::new();
let mut p_matrix = mat_4::Matrix::new();
let mut mvp_matrix = mat_4::Matrix::new();

//View行列とPerspective行列の設定
v_matrix.look_at(&[0., 0., 15.], &[0., 0., 0.], &[0., 1., 0.]);
p_matrix.perspective(width / height, 45., 0.1, 100.);

//最後にかけ合わせてMVP行列を完成させる
tmp_matrix
    .substitution(&p_matrix)
    .multiply(&v_matrix)
    .multiply(&m_matrix);

使用例2. 座標変換

lib.rs
//初期化
let mut m_matrix = mat_4::Matrix::new();
let rad = std::f32::consts::PI;
//単位行列 -> Y軸を中心ににradだけ回転 -> Z軸を中心にradだけ回転
m_matrix
    .set_identity()
    .rotate_around_y(rad)
    .rotate_around_z(rad);

//単位行列 -> 大きさを2倍 -> [1, 1, 3]に移動
m_matrix
    .set_identity()
    .scaling(&[2., 2., 2.,])
    .translation(&[1., 1., 3.,]);

関連記事

参照文献

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
What you can do with signing up
3