Current section

Files

Jump to
matrix_gleam src matrix mat3f.gleam
Raw

src/matrix/mat3f.gleam

//// 3x3 matrices of floats
import gleam/float
import gleam/list
import gleam/result
import gleam_community/maths
import matrix/internal/projection.{extend2, to_xy, to_xz, to_yz}
import matrix/mat2f
import vec/vec2
import vec/vec2f.{type Vec2f}
import vec/vec3.{type Vec3, Vec3}
import vec/vec3f.{type Vec3f, positive_x, positive_y, positive_z}
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)
/// A Mat3f representing the identity, i.e. `1.0` is on the diagonal.
pub const identity: Mat3f = vec3.Vec3(positive_x, positive_y, positive_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 }),
)
}
/// Creates a 3D rotation matrix from a normalized rotation `axis` and `angle` (in radians).
pub fn from_axis_angle(axis: Vec3f, angle: Float) -> Mat3f {
let axis = vec3f.normalize(axis)
let sin_a = maths.sin(angle)
let cos_a = maths.cos(angle)
let Vec3(x:, y:, z:) = axis
let Vec3(x: xsin, y: ysin, z: zsin) = vec3f.scale(axis, sin_a)
let Vec3(x: x2, y: y2, z: z2) = vec3f.multiply(axis, axis)
let omc = 1.0 -. cos_a
let xyomc = x *. y *. omc
let xzomc = x *. z *. omc
let yzomc = y *. z *. omc
from_cols(
Vec3(x2 *. omc +. cos_a, xyomc +. zsin, yzomc -. ysin),
Vec3(xyomc -. zsin, y2 *. omc +. cos_a, yzomc +. xsin),
Vec3(xzomc +. ysin, yzomc -. xsin, z2 *. omc +. cos_a),
)
}
/// Creates a 3D rotation matrix from `angle` in radians around the x axis.
pub fn from_rotation_x(angle: Float) -> Mat3f {
let sina = maths.sin(angle)
let cosa = maths.cos(angle)
from_cols(positive_x, Vec3(0.0, cosa, sina), Vec3(0.0, 0.0 -. sina, cosa))
}
/// Creates a 3D rotation matrix from `angle` in radians around the y axis.
pub fn from_rotation_y(angle: Float) -> Mat3f {
let sina = maths.sin(angle)
let cosa = maths.cos(angle)
from_cols(Vec3(cosa, 0.0, 0.0 -. sina), positive_y, Vec3(sina, 0.0, cosa))
}
/// Creates a 3D rotation matrix from `angle` in radians around the z axis.
pub fn from_rotation_z(angle: Float) -> Mat3f {
let sina = maths.sin(angle)
let cosa = maths.cos(angle)
from_cols(Vec3(cosa, sina, 0.0), Vec3(0.0 -. sina, cosa, 0.0), positive_z)
}
/// Creates an affine transformation matrix from the given 2D `translation`.
///
/// The resulting matrix can be used to transform 2D points and vectors.
pub fn from_translation(translation: vec2f.Vec2f) -> Mat3f {
from_cols(positive_x, positive_y, extend2(translation, 1.0))
}
/// Creates an affine transformation matrix from the given 2D rotation `angle` (in radians).
///
/// The resulting matrix can be used to transform 2D points and vectors.
pub fn from_angle(angle: Float) -> Mat3f {
from_rotation_z(angle)
}
/// Creates an affine transformation matrix from the given 2D `scale`, rotation `angle` (in radians), and `translation`.
///
/// The resulting matrix can be used to transform 2D points and vectors.
pub fn from_scale_angle_translation(
scale: Vec2f,
angle: Float,
translation: Vec2f,
) -> Mat3f {
let sina = maths.sin(angle)
let cosa = maths.cos(angle)
from_cols(
Vec3(cosa *. scale.x, sina *. scale.x, 0.0),
Vec3(float.negate(sina) *. scale.y, cosa *. scale.y, 0.0),
extend2(translation, 1.0),
)
}
/// Creates an affine transformation matrix from teh given non-uniform 2D `scale`.
///
/// The resulting matrix can be used to transform 2D points and vectors.
pub fn from_scale(scale: Vec2f) -> Mat3f {
from_diagonal(extend2(scale, 1.0))
}
/// 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 |> to_yz |> vec2.map(to_yz) |> mat2f.determinant
let xy = mat |> to_xz |> vec2.map(to_yz) |> vec2.swap |> mat2f.determinant
let xz = mat |> to_xy |> vec2.map(to_yz) |> mat2f.determinant
let yx = mat |> to_yz |> vec2.map(to_xz) |> vec2.swap |> mat2f.determinant
let yy = mat |> to_xz |> vec2.map(to_xz) |> mat2f.determinant
let yz = mat |> to_xy |> vec2.map(to_xz) |> vec2.swap |> mat2f.determinant
let zx = mat |> to_yz |> vec2.map(to_xy) |> mat2f.determinant
let zy = mat |> to_xz |> vec2.map(to_xy) |> vec2.swap |> mat2f.determinant
let zz = mat |> to_xy |> vec2.map(to_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 the given 2D vector as a point.
///
/// This is the equivalent of multiplying `rhs` as a 3D vector where `z` is `1`.
///
/// This function assumes that `mat` contains a valid affine transform.
pub fn transform_point2(mat: Mat3f, rhs: Vec2f) -> Vec2f {
mat2f.from_cols(to_xy(mat.x), to_xy(mat.y))
|> mat2f.mul_transpose_vec2(rhs)
|> vec2f.add(to_xy(mat.z))
}
/// Rotates the given 2D vector.
///
/// This is the equivalent of multiplying `rhs` as a 3D vector where `z` is `0`.
pub fn transform_vector2(mat: Mat3f, rhs: Vec2f) -> Vec2f {
mat2f.from_cols(to_xy(mat.x), to_xy(mat.y))
|> mat2f.mul_transpose_vec2(rhs)
}
/// Creates a left-handed view matrix using a facing direction and an up direction.
///
/// For a view coordinate system with `+X=right`, `+Y=up`, and `+Z=forward`.
pub fn look_to_lh(dir: Vec3f, up: Vec3f) -> Mat3f {
look_to_rh(vec3f.negate(dir), up)
}
/// Creates a right-handed view matrix using a facing direction and an up direction.
///
/// For a view coordinate system with `+X=right`, `+Y=up` and `+Z=back`.
pub fn look_to_rh(dir: Vec3f, up: Vec3f) {
let up = vec3f.normalize(up)
let f = vec3f.normalize(dir)
let s = f |> vec3f.cross(up) |> vec3f.normalize
let u = vec3f.cross(s, f)
let neg_f = vec3f.negate(f)
from_cols(
Vec3(s.x, u.x, neg_f.x),
Vec3(s.y, u.y, neg_f.y),
Vec3(s.z, u.z, neg_f.z),
)
}
/// Creates a left-handed view matrix using a camera position, a focal point and an up
/// direction.
///
/// For a view coordinate system with `+X=right`, `+Y=up` and `+Z=forward`.
pub fn look_at_lh(eye: Vec3f, center: Vec3f, up: Vec3f) -> Mat3f {
look_to_lh(vec3f.subtract(center, eye), up)
}
/// Creates a right-handed view matrix using a camera position, a focal point and an up
/// direction.
///
/// For a view coordinate system with `+X=right`, `+Y=up` and `+Z=back`.
pub fn look_at_rh(eye: Vec3f, center: Vec3f, up: Vec3f) -> Mat3f {
look_to_rh(vec3f.subtract(center, eye), up)
}
/// 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)
}
/// Takes the absolute value of each element in the `Mat3f`.
pub fn absolute_value(mat: Mat3f) -> Mat3f {
vec3.map(mat, vec3f.absolute_value)
}
/// 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)
}
/// Returns a matrix containing the reciprocal of each element of the `Mat3f`.
///
/// If any of the elements is zero, an error is returned.
pub fn reciprocal(mat: Mat3f) -> Result(Mat3f, Nil) {
vec3.map(mat, fn(column) {
column
|> vec3.map(float.divide(1.0, _))
|> vec3.result
})
|> vec3.result
}
/// Sums a list of `Mat3f`s.
pub fn sum(mats: List(Mat3f)) -> Mat3f {
list.fold(mats, zero, add)
}
/// Multiplies a list of `Mat3f`s.
pub fn product(mats: List(Mat3f)) -> Mat3f {
list.fold(mats, identity, multiply)
}