// 3x3 matrix. Naming: m. Column vectors: v' = M * v.
// Uses: rotations, change of basis, normal matrix, tangent frames, 2D homogeneous affine
// transforms (UI / texture-space / map-space), homographies.
///|
#valtype
pub(all) struct Mat3 {
m00 : Double
m01 : Double
m02 : Double
m10 : Double
m11 : Double
m12 : Double
m20 : Double
m21 : Double
m22 : Double
} derive(Eq, Debug)
///|
pub fn Mat3::identity() -> Mat3 {
{
m00: 1.0,
m01: 0.0,
m02: 0.0,
m10: 0.0,
m11: 1.0,
m12: 0.0,
m20: 0.0,
m21: 0.0,
m22: 1.0,
}
}
///|
pub fn Mat3::from_cols(c0 : Vec3, c1 : Vec3, c2 : Vec3) -> Mat3 {
{
m00: c0.x,
m01: c1.x,
m02: c2.x,
m10: c0.y,
m11: c1.y,
m12: c2.y,
m20: c0.z,
m21: c1.z,
m22: c2.z,
}
}
///|
pub fn Mat3::from_rows(r0 : Vec3, r1 : Vec3, r2 : Vec3) -> Mat3 {
{
m00: r0.x,
m01: r0.y,
m02: r0.z,
m10: r1.x,
m11: r1.y,
m12: r1.z,
m20: r2.x,
m21: r2.y,
m22: r2.z,
}
}
///|
pub fn Mat3::col(self : Mat3, i : Int) -> Vec3 {
match i {
0 => { x: self.m00, y: self.m10, z: self.m20, }
1 => { x: self.m01, y: self.m11, z: self.m21, }
2 => { x: self.m02, y: self.m12, z: self.m22, }
_ => abort("Mat3::col: index out of range")
}
}
///|
pub fn Mat3::row(self : Mat3, i : Int) -> Vec3 {
match i {
0 => { x: self.m00, y: self.m01, z: self.m02, }
1 => { x: self.m10, y: self.m11, z: self.m12, }
2 => { x: self.m20, y: self.m21, z: self.m22, }
_ => abort("Mat3::row: index out of range")
}
}
///|
pub fn Mat3::diagonal(x : Double, y : Double, z : Double) -> Mat3 {
{ ..Mat3::identity(), m00: x, m11: y, m22: z, }
}
///|
/// Matrix product: `a * b` applies `b` first.
pub impl Mul for Mat3 with fn mul(a, b) {
{
m00: a.m00 * b.m00 + a.m01 * b.m10 + a.m02 * b.m20,
m01: a.m00 * b.m01 + a.m01 * b.m11 + a.m02 * b.m21,
m02: a.m00 * b.m02 + a.m01 * b.m12 + a.m02 * b.m22,
m10: a.m10 * b.m00 + a.m11 * b.m10 + a.m12 * b.m20,
m11: a.m10 * b.m01 + a.m11 * b.m11 + a.m12 * b.m21,
m12: a.m10 * b.m02 + a.m11 * b.m12 + a.m12 * b.m22,
m20: a.m20 * b.m00 + a.m21 * b.m10 + a.m22 * b.m20,
m21: a.m20 * b.m01 + a.m21 * b.m11 + a.m22 * b.m21,
m22: a.m20 * b.m02 + a.m21 * b.m12 + a.m22 * b.m22,
}
}
///|
pub fn Mat3::mul_vec3(self : Mat3, v : Vec3) -> Vec3 {
{
x: self.m00 * v.x + self.m01 * v.y + self.m02 * v.z,
y: self.m10 * v.x + self.m11 * v.y + self.m12 * v.z,
z: self.m20 * v.x + self.m21 * v.y + self.m22 * v.z,
}
}
///|
pub fn Mat3::transpose(self : Mat3) -> Mat3 {
{
m00: self.m00,
m01: self.m10,
m02: self.m20,
m10: self.m01,
m11: self.m11,
m12: self.m21,
m20: self.m02,
m21: self.m12,
m22: self.m22,
}
}
///|
pub fn Mat3::determinant(self : Mat3) -> Double {
self.m00 * (self.m11 * self.m22 - self.m12 * self.m21) -
self.m01 * (self.m10 * self.m22 - self.m12 * self.m20) +
self.m02 * (self.m10 * self.m21 - self.m11 * self.m20)
}
///|
/// General inverse; `None` when singular (det == 0) or not finite.
pub fn Mat3::inverse(self : Mat3) -> Mat3? {
let c00 = self.m11 * self.m22 - self.m12 * self.m21
let c01 = self.m12 * self.m20 - self.m10 * self.m22
let c02 = self.m10 * self.m21 - self.m11 * self.m20
let det = self.m00 * c00 + self.m01 * c01 + self.m02 * c02
if det == 0.0 || det.is_nan() || det.is_inf() {
return None
}
let i = 1.0 / det
Some({
m00: c00 * i,
m01: (self.m02 * self.m21 - self.m01 * self.m22) * i,
m02: (self.m01 * self.m12 - self.m02 * self.m11) * i,
m10: c01 * i,
m11: (self.m00 * self.m22 - self.m02 * self.m20) * i,
m12: (self.m02 * self.m10 - self.m00 * self.m12) * i,
m20: c02 * i,
m21: (self.m01 * self.m20 - self.m00 * self.m21) * i,
m22: (self.m00 * self.m11 - self.m01 * self.m10) * i,
})
}
///|
/// (M^-1)^T, the matrix that transforms surface normals. `None` when singular.
pub fn Mat3::normal_matrix(self : Mat3) -> Mat3? {
match self.inverse() {
Some(i) => Some(i.transpose())
None => None
}
}
///|
pub fn Mat3::from_quat(q : Quat) -> Mat3 {
let xx = q.x * q.x
let yy = q.y * q.y
let zz = q.z * q.z
let xy = q.x * q.y
let xz = q.x * q.z
let yz = q.y * q.z
let wx = q.w * q.x
let wy = q.w * q.y
let wz = q.w * q.z
{
m00: 1.0 - 2.0 * (yy + zz),
m01: 2.0 * (xy - wz),
m02: 2.0 * (xz + wy),
m10: 2.0 * (xy + wz),
m11: 1.0 - 2.0 * (xx + zz),
m12: 2.0 * (yz - wx),
m20: 2.0 * (xz - wy),
m21: 2.0 * (yz + wx),
m22: 1.0 - 2.0 * (xx + yy),
}
}
///|
pub fn Mat3::rotation_x(a : Double) -> Mat3 {
let c = @math.cos(a)
let s = @math.sin(a)
{ ..Mat3::identity(), m11: c, m12: -s, m21: s, m22: c, }
}
///|
pub fn Mat3::rotation_y(a : Double) -> Mat3 {
let c = @math.cos(a)
let s = @math.sin(a)
{ ..Mat3::identity(), m00: c, m02: s, m20: -s, m22: c, }
}
///|
pub fn Mat3::rotation_z(a : Double) -> Mat3 {
let c = @math.cos(a)
let s = @math.sin(a)
{ ..Mat3::identity(), m00: c, m01: -s, m10: s, m11: c, }
}
///|
/// Rodrigues rotation about `axis` (normalised internally).
pub fn Mat3::from_axis_angle(axis : Vec3, angle : Double) -> Mat3 {
match axis.try_normalize() {
None => Mat3::identity()
Some(a) => {
let c = @math.cos(angle)
let s = @math.sin(angle)
let t = 1.0 - c
{
m00: c + a.x * a.x * t,
m01: a.x * a.y * t - a.z * s,
m02: a.x * a.z * t + a.y * s,
m10: a.y * a.x * t + a.z * s,
m11: c + a.y * a.y * t,
m12: a.y * a.z * t - a.x * s,
m20: a.z * a.x * t - a.y * s,
m21: a.z * a.y * t + a.x * s,
m22: c + a.z * a.z * t,
}
}
}
}
///|
/// Orientation matrix (columns = right, up, back) whose local -Z is `forward` and whose
/// local +Y is closest to `up`. Degenerate inputs fall back to a valid orthonormal basis.
pub fn Mat3::look_rotation(forward : Vec3, up : Vec3) -> Mat3 {
let z = (-forward).normalize()
let z = if z.length_sq() == 0.0 { Vec3::unit_z() } else { z }
let mut x = up.cross(z)
if x.length_sq() < 1.0e-24 {
x = z.any_perpendicular()
} else {
x = x.normalize()
}
let y = z.cross(x)
Mat3::from_cols(x, y, z)
}
///|
pub fn Mat3::approx_eq(self : Mat3, o : Mat3, eps : Double) -> Bool {
approx(self.m00, o.m00, eps) &&
approx(self.m01, o.m01, eps) &&
approx(self.m02, o.m02, eps) &&
approx(self.m10, o.m10, eps) &&
approx(self.m11, o.m11, eps) &&
approx(self.m12, o.m12, eps) &&
approx(self.m20, o.m20, eps) &&
approx(self.m21, o.m21, eps) &&
approx(self.m22, o.m22, eps)
}
///|
/// Column-major 9 values (GLSL `mat3` / WGSL `mat3x3` memory order, tightly packed).
pub fn Mat3::to_col_major(self : Mat3) -> FixedArray[Double] {
[
self.m00,
self.m10,
self.m20,
self.m01,
self.m11,
self.m21,
self.m02,
self.m12,
self.m22,
]
}
// ---------------------------------------------------------------------------
// 2D homogeneous affine / projective transforms (points are (x, y, 1)).
// ---------------------------------------------------------------------------
///|
pub fn Mat3::translation2d(tx : Double, ty : Double) -> Mat3 {
{ ..Mat3::identity(), m02: tx, m12: ty, }
}
///|
pub fn Mat3::scaling2d(sx : Double, sy : Double) -> Mat3 {
{ ..Mat3::identity(), m00: sx, m11: sy, }
}
///|
/// Counter-clockwise rotation about the origin (y-up frame).
pub fn Mat3::rotation2d(angle : Double) -> Mat3 {
Mat3::rotation_z(angle)
}
///|
/// Translate * Rotate * Scale (scale applied first).
pub fn Mat3::from_trs2d(t : Vec2, angle : Double, s : Vec2) -> Mat3 {
let c = @math.cos(angle)
let sn = @math.sin(angle)
{
m00: c * s.x,
m01: -sn * s.y,
m02: t.x,
m10: sn * s.x,
m11: c * s.y,
m12: t.y,
m20: 0.0,
m21: 0.0,
m22: 1.0,
}
}
///|
/// Affine point transform (assumes last row = 0 0 1).
pub fn Mat3::mul_point2(self : Mat3, p : Vec2) -> Vec2 {
{
x: self.m00 * p.x + self.m01 * p.y + self.m02,
y: self.m10 * p.x + self.m11 * p.y + self.m12,
}
}
///|
/// Direction transform (ignores translation).
pub fn Mat3::mul_dir2(self : Mat3, d : Vec2) -> Vec2 {
{ x: self.m00 * d.x + self.m01 * d.y, y: self.m10 * d.x + self.m11 * d.y, }
}
///|
/// Projective (homography) point transform with divide by w. `None` if w == 0.
pub fn Mat3::project_point2(self : Mat3, p : Vec2) -> Vec2? {
let w = self.m20 * p.x + self.m21 * p.y + self.m22
if w == 0.0 {
return None
}
let iw = 1.0 / w
Some({
x: (self.m00 * p.x + self.m01 * p.y + self.m02) * iw,
y: (self.m10 * p.x + self.m11 * p.y + self.m12) * iw,
})
}