Current section
Files
Jump to
Current section
Files
src/vec/vec3i.gleam
import gleam/int
import gleam/list
import gleam/order.{type Order}
import vec/vec3.{type Vec3, Vec3}
/// A 3-element structure that can be used to represent 3D coordinates or any
/// other triplet of integer values.
///
pub type Vec3i =
Vec3(Int)
/// Zero vector, a vector with all components set to `0`.
///
pub const zero = Vec3(0, 0, 0)
/// One vector, a vector with all components set to `1`.
///
pub const one = Vec3(1, 1, 1)
/// X axis unit-vector, a vector with the `x` component set to `1` and all
/// other components set to `0`.
///
pub const positive_x = Vec3(1, 0, 0)
/// Y axis unit-vector, a vector with the `y` component set to `1` and all
/// other components set to `0`.
///
pub const positive_y = Vec3(0, 1, 0)
/// Z axis unit-vector, a vector with the `z` component set to `1` and all
/// other components set to `0`.
///
pub const positive_z = Vec3(0, 0, 1)
/// Negative X axis unit-vector, a vector with the `x` component set to `-1`
/// and all other components set to `0`.
///
pub const negative_x = Vec3(-1, 0, 0)
/// Negative Y axis unit-vector, a vector with the `y` component set to `-1`
/// and all other components set to `0`.
///
pub const negative_y = Vec3(0, -1, 0)
/// Negative Z axis unit-vector, a vector with the `z` component set to `-1`
/// and all other components set to `0`.
///
pub const negative_z = Vec3(0, 0, -1)
/// Returns a new vector with all components clamped between a lower and upper
/// bound.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> clamp(
/// Vec3(10, 21, -54),
/// Vec3(14, 18, 323),
/// )
/// // -> Vec3(12, 18, 323)
/// ```
///
pub fn clamp(vector: Vec3i, start_bound: Vec3i, stop_bound: Vec3i) -> Vec3i {
vec3.map3(vector, start_bound, stop_bound, int.clamp)
}
/// Compares two vectors, returning the smaller of the two.
///
/// ## Examples
///
/// ```gleam
/// min(Vec3(12, -34, 420), Vec3(10, 21, -54))
/// // -> Vec3(10, -34, -54)
/// ```
///
pub fn min(a: Vec3i, b: Vec3i) -> Vec3i {
a |> vec3.map2(b, int.min)
}
/// Compares two vectors, returning the larger of the two.
///
/// ## Examples
///
/// ```gleam
/// max(Vec3(12, -34, 420), Vec3(14, -93, 323))
/// // -> Vec3(14, -34, 420)
/// ```
///
pub fn max(a: Vec3i, b: Vec3i) -> Vec3i {
a |> vec3.map2(b, int.max)
}
/// Returns a new vector with all elements in absolute values.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> absolute_value
/// // -> Vec3(12, 34, 420)
/// ```
///
pub fn absolute_value(vector: Vec3i) -> Vec3i {
vector |> vec3.map(int.absolute_value)
}
/// Takes an int vector and returns its value as a float vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> to_vec3f
/// // -> Vec3(12.0, -34.0, 420.0)
/// ```
///
pub fn to_vec3f(vector: Vec3i) -> Vec3(Float) {
vector |> vec3.map(int.to_float)
}
/// Returns a new vector with all elements negated.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> negate
/// // -> Vec3(-12, 34, -420)
/// ```
///
pub fn negate(vector: Vec3i) -> Vec3i {
vector |> vec3.map(int.negate)
}
/// Sums a list of vectors.
///
/// ## Examples
///
/// ```gleam
/// [
/// Vec3(12, -34, 420),
/// Vec3(21, 45, -20),
/// Vec3(33, 0, -200),
/// ]
/// |> sum
/// // -> Vec3(66, 11, 200)
/// ```
///
pub fn sum(vectors: List(Vec3i)) -> Vec3i {
vectors |> list.fold(vec3.splat(0), add)
}
/// Multiplies a list of vectors and returns the product.
///
/// ## Examples
///
/// ```gleam
/// [
/// Vec3(12, -34, 420),
/// Vec3(21, -10, 999),
/// Vec3(32, 20, 0),
/// ]
/// |> product
/// // -> Vec3(8064, 6800, 0)
/// ```
///
pub fn product(vectors: List(Vec3i)) -> Vec3i {
vectors |> list.fold(vec3.splat(1), multiply)
}
/// Computes the remainder of an integer vector division of inputs as a
/// `Result`.
///
/// ## Examples
///
/// ```gleam
/// Vec3(13, -13, 13) |> remainder(Vec3(3, 3, -3))
/// // -> Ok(Vec3(1, -1, 1))
/// ```
///
/// ```gleam
/// Vec3(12, -34, 420) |> remainder(Vec3(0, 1, 2))
/// // -> Error(Nil)
/// ```
///
pub fn remainder(dividend: Vec3i, by divisor: Vec3i) -> Result(Vec3i, Nil) {
dividend |> vec3.map2(divisor, int.remainder) |> vec3.result
}
/// Returns the modulo of the inputs as a `Result`.
///
/// ## Examples
///
/// ```gleam
/// Vec3(13, -13, 13) |> modulo(Vec3(3, 3, -3))
/// // -> Ok(Vec3(1, 2, -2))
/// ```
///
pub fn modulo(dividend: Vec3i, by divisor: Vec3i) -> Result(Vec3i, Nil) {
dividend |> vec3.map2(divisor, int.modulo) |> vec3.result
}
/// Returns division of the inputs as a `Result`.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> divide(Vec3(2, 5, 4))
/// // -> Ok(Vec3(6, -6, 105))
/// ```
///
/// ```gleam
/// Vec3(12, -34, 420) |> divide(Vec3(0, 5, 4))
/// // -> Error(Nil)
/// ```
///
pub fn divide(dividend: Vec3i, by divisor: Vec3i) -> Result(Vec3i, Nil) {
dividend |> vec3.map2(divisor, int.divide) |> vec3.result
}
/// Performs a *floored* integer vector division, which means that the result
/// will always be rounded towards negative infinity.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> floor_divide(Vec3(2, 5, 4))
/// // -> Ok(Vec3(6, -7, 105))
/// ```
///
/// ```gleam
/// Vec3(12, -34, 420) |> floor_divide(Vec3(0, 5, 4))
/// // -> Error(Nil)
/// ```
///
pub fn floor_divide(dividend: Vec3i, by divisor: Vec3i) -> Result(Vec3i, Nil) {
dividend |> vec3.map2(divisor, int.floor_divide) |> vec3.result
}
/// Adds two vectors together.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> add(Vec3(21, 45, -20))
/// // -> Vec3(33, 11, 400)
/// ```
///
pub fn add(a: Vec3i, b: Vec3i) -> Vec3i {
a |> vec3.map2(b, int.add)
}
/// Multiplies two vectors together.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> multiply(Vec3(2, -3, 0))
/// // -> Vec3(24, 102, 0)
/// ```
///
pub fn multiply(a: Vec3i, b: Vec3i) -> Vec3i {
a |> vec3.map2(b, int.multiply)
}
/// Subtracts one vector from another.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> subtract(Vec3(7, -45, 20))
/// // -> Vec3(5, 11, 400)
/// ```
///
pub fn subtract(a: Vec3i, b: Vec3i) -> Vec3i {
a |> vec3.map2(b, int.subtract)
}
/// Run a function for each vector between vectors `from` and `to`.
///
/// `from` is inclusive, and `to` is exclusive.
///
/// ## Examples
///
/// ```gleam
/// range(from: zero, to: vec3.splat(3), with: dict.new(), run: fn(acc, v) {
/// acc |> dict.insert(v, v.x + v.y + v.z)
/// })
/// ```
///
pub fn range(
from start: Vec3i,
to stop: Vec3i,
with acc: acc,
run reducer: fn(acc, Vec3i) -> acc,
) -> acc {
int.range(from: start.z, to: stop.z, with: acc, run: fn(acc, z) {
int.range(from: start.y, to: stop.y, with: acc, run: fn(acc, y) {
int.range(from: start.x, to: stop.x, with: acc, run: fn(acc, x) {
reducer(acc, Vec3(x, y, z))
})
})
})
}
/// Returns the squared length (squared magnitude) of the vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> length_squared
/// // -> 177_700
/// ```
///
pub fn length_squared(vector: Vec3i) -> Int {
vector
|> vec3.to_list
|> list.map(fn(element) { element * element })
|> int.sum
}
/// Returns the length (magnitude) of the vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> length
/// // -> 421.54
/// ```
///
pub fn length(vector: Vec3i) -> Float {
let assert Ok(length) = vector |> length_squared |> int.square_root
length
}
/// Compares two vector's lengths, returning an `Order`:
/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.
///
/// ## Examples
///
/// ```gleam
/// compare_length(Vec3(12, -34, 420), Vec3(2, 3, 4))
/// // -> Gt
/// ```
///
pub fn compare_length(a: Vec3i, with b: Vec3i) -> Order {
int.compare(a |> length_squared, b |> length_squared)
}
/// Returns the squared distance between two vectors.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> distance_squared(Vec3(2, 3, 4))
/// // -> 174_525
/// ```
///
pub fn distance_squared(a: Vec3i, with b: Vec3i) -> Int {
a |> subtract(b) |> length_squared
}
/// Returns the distance between two vectors.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> distance(Vec3(2, 3, 4))
/// // -> 417.76
/// ```
///
pub fn distance(a: Vec3i, with b: Vec3i) -> Float {
let assert Ok(distance) = distance_squared(a, b) |> int.square_root
distance
}
/// Compares two vector's distances to a vector, returning an `Order`:
/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.
///
/// ## Examples
///
/// ```gleam
/// compare_distance(Vec3(12, -34, 420), Vec3(2, 3, 4), Vec3(-25, 67, 194))
/// // -> Gt
/// ```
///
pub fn compare_distance(a: Vec3i, with b: Vec3i, to vector: Vec3i) -> Order {
int.compare(a |> distance_squared(vector), b |> distance_squared(vector))
}
/// Returns a new vector containing the elements multiplies by `scalar`.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> scale(2)
/// // -> Vec3(24, -68, 840)
/// ```
///
pub fn scale(vector: Vec3i, by scalar: Int) -> Vec3i {
vector |> vec3.map(int.multiply(_, scalar))
}
/// Returns the cross product of two vectors.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> cross(Vec3(2, 3, 4))
/// // -> Vec3(-1396, 792, 104)
/// ```
///
pub fn cross(a: Vec3i, b: Vec3i) -> Vec3i {
Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x)
}
/// Returns the dot product of two vectors.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> dot(Vec3(2, 3, 4))
/// // -> 1602
/// ```
///
pub fn dot(a: Vec3i, b: Vec3i) -> Int {
a |> multiply(b) |> vec3.to_list |> int.sum
}
/// Returns the projection of a vector on another vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> project(Vec3(2, 3, 4))
/// // -> Vec3(110, 165, 220)
/// ```
///
pub fn project(a: Vec3i, on b: Vec3i) -> Vec3i {
b |> scale(dot(a, b) / dot(b, b))
}
/// Returns a new vector resulting from sliding this vector along a plane
/// defined by the given normal vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> slide(Vec3(2, 3, 4))
/// // -> Vec3(-98, -199, 200)
/// ```
///
pub fn slide(a: Vec3i, on b: Vec3i) -> Vec3i {
a |> subtract(a |> project(b))
}
/// Returns the reflection of a vector through a plane defined by the given
/// normal vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> reflect(Vec3(2, 3, 4))
/// // -> Vec3(208, 364, 20)
/// ```
///
pub fn reflect(vector: Vec3i, through normal: Vec3i) -> Vec3i {
vector |> project(normal) |> scale(2) |> subtract(vector)
}
/// Returns the mirror of a vector through a plane defined by the given normal
/// vector.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> mirror(Vec3(2, 3, 4))
/// // -> Vec3(-208, -364, -20)
/// ```
///
pub fn mirror(vector: Vec3i, through normal: Vec3i) -> Vec3i {
vector |> reflect(normal) |> negate
}
/// Rotate a vector by an angles for each axes (in 90 degree steps).
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420) |> rotate(Vec3(2, 3, 4))
/// // -> Vec3(420, 34, 12)
/// ```
///
pub fn rotate(vector: Vec3i, by angles: Vec3i) -> Vec3i {
let vector = case angles.x |> int.modulo(4) {
Ok(1) -> Vec3(vector.x, -vector.z, vector.y)
Ok(2) -> Vec3(vector.x, -vector.y, -vector.z)
Ok(3) -> Vec3(vector.x, vector.z, -vector.y)
_ -> vector
}
let vector = case angles.y |> int.modulo(4) {
Ok(1) -> Vec3(vector.z, vector.y, -vector.x)
Ok(2) -> Vec3(-vector.x, vector.y, -vector.z)
Ok(3) -> Vec3(-vector.z, vector.y, vector.x)
_ -> vector
}
let vector = case angles.z |> int.modulo(4) {
Ok(1) -> Vec3(-vector.y, vector.x, vector.z)
Ok(2) -> Vec3(-vector.x, -vector.y, vector.z)
Ok(3) -> Vec3(vector.y, -vector.x, vector.z)
_ -> vector
}
vector
}
/// Return the equivalent of `vector |> subtract(position) |> fun |> add(position)`.
///
/// ## Examples
///
/// ```gleam
/// Vec3(12, -34, 420)
/// |> anchor_position(Vec3(20, 40, 0), scale(_, 2))
/// // -> Vec3(4, -108, 840)
/// ```
///
pub fn anchor_position(
vector: Vec3i,
at position: Vec3i,
then fun: fn(Vec3i) -> Vec3i,
) -> Vec3i {
vector |> subtract(position) |> fun |> add(position)
}