Current section
Files
Jump to
Current section
Files
src/matrix/mat3f.gleam
//// 3x3 matrices of floats
import gleam/float
import gleam/result
import matrix/mat2f
import vec/vec2.{type Vec2, Vec2}
import vec/vec3.{type Vec3, Vec3}
import vec/vec3f.{type Vec3f}
import vec/vec4f
/// Mat3f is a 3x3 column-major matrix of `Float`s.
pub type Mat3f =
Vec3(Vec3f)
/// A Mat3f with all elements set to `0.0`
pub const zero: Mat3f = vec3.Vec3(vec3f.zero, vec3f.zero, vec3f.zero)
const x: Vec3f = Vec3(1.0, 0.0, 0.0)
const y: Vec3f = Vec3(0.0, 1.0, 0.0)
const z: Vec3f = Vec3(0.0, 0.0, 1.0)
/// A Mat3f representing the identity, i.e. `1.0` is on the diagonal.
pub const identity: Mat3f = vec3.Vec3(x, y, z)
/// Constructs a `Mat3f` from its components:
/// ```text
/// | a d g |
/// | b e h |
/// | c f i |
/// ```
pub fn new(
a: Float,
b: Float,
c: Float,
d: Float,
e: Float,
f: Float,
g: Float,
h: Float,
i: Float,
) -> Mat3f {
Vec3(Vec3(a, b, c), Vec3(d, e, f), Vec3(g, h, i))
}
/// Constructs a `Mat3f` from its three columns.
/// ```text
/// | ax bx cx |
/// | ay by cy |
/// | az bz cz |
/// ```
pub fn from_cols(a: Vec3f, b: Vec3f, c: Vec3f) -> Mat3f {
Vec3(a, b, c)
}
/// Constructs a `Mat3f` with the given diagonal and all other entries set to 0.
/// ```text
/// | x 0.0 0.0 |
/// | 0.0 y 0.0 |
/// | 0.0 0.0 z |
/// ```
pub fn from_diagonal(diag: Vec3f) -> Mat3f {
new(diag.x, 0.0, 0.0, 0.0, diag.y, 0.0, 0.0, 0.0, diag.z)
}
/// Constructs a 3D rotation matrix from the given quaternion, represented as a `vec4.Vec4(Float)`.
pub fn from_quaternion(q: vec4f.Vec4f) -> Mat3f {
let rotation = vec4f.normalize(q)
let x2 = rotation.x +. rotation.x
let y2 = rotation.y +. rotation.y
let z2 = rotation.z +. rotation.z
let xx = rotation.x *. x2
let xy = rotation.x *. y2
let xz = rotation.x *. z2
let yy = rotation.y *. y2
let yz = rotation.y *. z2
let zz = rotation.z *. z2
let wx = rotation.w *. x2
let wy = rotation.w *. y2
let wz = rotation.w *. z2
from_cols(
Vec3(1.0 -. { yy +. zz }, xy +. wz, xz -. wy),
Vec3(xy -. wz, 1.0 -. { xx +. zz }, yz +. wx),
Vec3(xz +. wy, yz -. wx, 1.0 -. { xx +. yy }),
)
}
/// Transposes the `Mat3f` along the diagonal.
pub fn transpose(mat: Mat3f) -> Mat3f {
new(
mat.x.x,
mat.y.x,
mat.z.x,
mat.x.y,
mat.y.y,
mat.z.y,
mat.x.z,
mat.y.z,
mat.z.z,
)
}
/// Returns the determinant for the `Mat3f`.
pub fn determinant(mat: Mat3f) -> Float {
let Vec3(Vec3(a1, b1, c1), Vec3(a2, b2, c2), Vec3(a3, b3, c3)) = mat
{ a1 *. b2 *. c3 }
-. { a1 *. b3 *. c2 }
-. { a2 *. b1 *. c3 }
+. { a2 *. b3 *. c1 }
+. { a3 *. b1 *. c2 }
-. { a3 *. b2 *. c1 }
}
/// Inverts the `Mat3f`, returning an error if the determinant is zero.
pub fn inverse(mat: Mat3f) -> Result(Mat3f, Nil) {
use inv_det <- result.map(float.divide(1.0, determinant(mat)))
// Reference: https://mathworld.wolfram.com/MatrixInverse.html
//
// The below is not the most efficient form (we could extract the individual
// components and inline the multiplication), but it is the most direct mapping
// to what the Reference above states, nine determinants based on projections
// of the original matrix.
let xx = mat |> yz_ |> vec2.map(yz_) |> mat2f.determinant
let xy = mat |> xz_ |> vec2.map(yz_) |> vec2.swap |> mat2f.determinant
let xz = mat |> xy_ |> vec2.map(yz_) |> mat2f.determinant
let yx = mat |> yz_ |> vec2.map(xz_) |> vec2.swap |> mat2f.determinant
let yy = mat |> xz_ |> vec2.map(xz_) |> mat2f.determinant
let yz = mat |> xy_ |> vec2.map(xz_) |> vec2.swap |> mat2f.determinant
let zx = mat |> yz_ |> vec2.map(xy_) |> mat2f.determinant
let zy = mat |> xz_ |> vec2.map(xy_) |> vec2.swap |> mat2f.determinant
let zz = mat |> xy_ |> vec2.map(xy_) |> mat2f.determinant
scale(new(xx, xy, xz, yx, yy, yz, zx, zy, zz), inv_det)
}
/// Transforms a `Vec3f` by this `Mat3f`.
pub fn mul_vec3(mat: Mat3f, rhs: Vec3f) -> Vec3f {
vec3.map2(mat, rhs, vec3f.scale)
|> vec3.to_list
|> vec3f.sum
}
/// Transforms a `Vec3f` by the transpose of this `Mat3f`.
pub fn mul_transpose_vec3(mat: Mat3f, rhs: Vec3f) -> Vec3f {
vec3.map(mat, vec3f.dot(_, rhs))
}
/// Negates all elements of the `Mat3f`
pub fn negate(mat: Mat3f) -> Mat3f {
vec3.map(mat, vec3f.negate)
}
/// Adds two `Mat3f` together.
pub fn add(a: Mat3f, b: Mat3f) -> Mat3f {
vec3.map2(a, b, vec3f.add)
}
/// Subtracts one `Mat3f` from the other.
pub fn subtract(a: Mat3f, b: Mat3f) -> Mat3f {
vec3.map2(a, b, vec3f.subtract)
}
/// Multiplies two `Mat3f` together.
pub fn multiply(a: Mat3f, b: Mat3f) -> Mat3f {
vec3.map(b, mul_vec3(a, _))
}
/// Divides one `Mat3f` by another. Equivalent to multiplying the inverse of the second matrix.
pub fn divide(a: Mat3f, b: Mat3f) -> Result(Mat3f, Nil) {
use inv_b <- result.map(inverse(b))
multiply(a, inv_b)
}
/// Scales the `Mat3f` by a `Float` factor.
pub fn scale(mat: Mat3f, scale: Float) -> Mat3f {
vec3.map(mat, vec3f.scale(_, scale))
}
/// Scales the `Mat3f` by a `Vec3f`.
///
/// This is faster than creating a diagonal scaling matrix and then multiplying that.
pub fn scale_diagonal(mat: Mat3f, scale: Vec3f) -> Mat3f {
vec3.map2(mat, scale, vec3f.scale)
}
fn xz_(v: Vec3(a)) -> Vec2(a) {
Vec2(x: v.x, y: v.z)
}
fn xy_(v: Vec3(a)) -> Vec2(a) {
Vec2(x: v.x, y: v.y)
}
fn yz_(v: Vec3(a)) -> Vec2(a) {
Vec2(x: v.y, y: v.z)
}