Current section
Files
Jump to
Current section
Files
src/vec/vec2f.gleam
import gleam/float
import gleam/list
import gleam/order.{type Order}
import gleam_community/maths
import vec/vec2.{type Vec2, Vec2}
/// A 2-element structure that can be used to represent 2D coordinates or any
/// other pair of float values.
///
pub type Vec2f =
Vec2(Float)
/// Zero vector, a vector with all components set to `0.0`.
///
pub const zero = Vec2(0.0, 0.0)
/// One vector, a vector with all components set to `1.0`.
///
pub const one = Vec2(1.0, 1.0)
/// X axis unit-vector, a vector with the `x` component set to `1.0` and `y`
/// component set to `0.0`.
///
pub const positive_x = Vec2(1.0, 0.0)
/// Y axis unit-vector, a vector with the `y` component set to `1.0` and `x`
/// component set to `0.0`.
///
pub const positive_y = Vec2(0.0, 1.0)
/// Negative X axis unit-vector, a vector with the `x` component set to `-1.0`
/// and `y` component set to `0.0`.
///
pub const negative_x = Vec2(-1.0, 0.0)
/// Negative Y axis unit-vector, a vector with the `y` component set to `-1.0`
/// and `x` component set to `0.0`.
///
pub const negative_y = Vec2(0.0, -1.0)
/// Returns a new vector with all components clamped between a lower and upper
/// bound.
///
/// ## Examples
///
/// ```gleam
/// assert
/// Vec2(1.2, -3.4)
/// |> clamp(Vec2(1.0, 2.1), Vec2(1.4, 18.2))
/// == Vec2(1.2, 2.1)
/// ```
///
pub fn clamp(vector: Vec2f, start_bound: Vec2f, stop_bound: Vec2f) -> Vec2f {
vec2.map3(vector, start_bound, stop_bound, float.clamp)
}
/// Checks for equality of two vectors within a tolerance, returning an `Bool`.
///
/// ## Examples
///
/// ```gleam
/// assert
/// Vec2(1.2, -3.4)
/// |> loosely_equals(Vec2(1.25, -3.43), tolerating: 0.1)
/// == True
/// ```
///
pub fn loosely_equals(
a: Vec2f,
with b: Vec2f,
tolerating tolerance: Float,
) -> Bool {
case a |> vec2.map2(b, fn(a, b) { float.loosely_equals(a, b, tolerance) }) {
Vec2(True, True) -> True
_ -> False
}
}
/// Compares two vectors, returning the smaller of the two.
///
/// ## Examples
///
/// ```gleam
/// assert min(Vec2(1.2, -3.4), Vec2(1.0, 2.1)) == Vec2(1.0, -3.4)
/// ```
///
pub fn min(a: Vec2f, b: Vec2f) -> Vec2f {
a |> vec2.map2(b, float.min)
}
/// Compares two vectors, returning the larger of the two.
///
/// ## Examples
///
/// ```gleam
/// assert max(Vec2(1.2, -3.4), Vec2(1.4, -9.3)) == Vec2(1.4, -3.4)
/// ```
///
pub fn max(a: Vec2f, b: Vec2f) -> Vec2f {
a |> vec2.map2(b, float.max)
}
/// Returns a new vector with all elements rounded to the next highest whole
/// number as a `Float`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.6) |> ceiling == Vec2(2.0, -3.0)
/// ```
///
pub fn ceiling(vector: Vec2f) -> Vec2f {
vector |> vec2.map(float.ceiling)
}
/// Returns a new vector with all elements rounded to the next lowest whole
/// number as an `Float`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.6) |> floor == Vec2(1.0, -4.0)
/// ```
///
pub fn floor(vector: Vec2f) -> Vec2f {
vector |> vec2.map(float.floor)
}
/// Returns a new vector with all elements rounded to the nearest whole number
/// as an `Int`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.6) |> round == Vec2(1, -4)
/// ```
///
pub fn round(vector: Vec2f) -> Vec2(Int) {
vector |> vec2.map(float.round)
}
/// Returns a new vector with all elements truncated as an `Int`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2323232827383238, -3.656565) |> truncate == Vec2(1, -3)
/// ```
///
pub fn truncate(vector: Vec2f) -> Vec2(Int) {
vector |> vec2.map(float.truncate)
}
/// Returns a new vector with all elements converted to a given precision.
///
/// ## Examples
///
/// ```gleam
/// assert
/// Vec2(2.43434348473, -3.656565)
/// |> to_precision(2)
/// == Vec2(2.43, -3.66)
/// ```
///
/// ```gleam
/// assert
/// Vec2(547_890.453444, -3.656565)
/// |> to_precision(-3)
/// == Vec2(548_000.0, 0.0)
/// ```
///
pub fn to_precision(vector: Vec2f, precision: Int) -> Vec2f {
vector |> vec2.map(float.to_precision(_, precision))
}
/// Returns a new vector with all elements in absolute values.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> absolute_value == Vec2(1.2, 3.4)
/// ```
///
pub fn absolute_value(vector: Vec2f) -> Vec2f {
vector |> vec2.map(float.absolute_value)
}
/// Returns a new vector with all elements negated.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> negate == Vec2(-1.2, 3.4)
/// ```
///
pub fn negate(vector: Vec2f) -> Vec2f {
vector |> vec2.map(float.negate)
}
/// Sums a list of vectors.
///
/// ## Examples
///
/// ```gleam
/// assert
/// [Vec2(1.2, -3.4), Vec2(2.1, 4.5), Vec2(3.3, 0.0)]
/// |> sum
/// == Vec2(6.6, 1.1)
/// ```
///
pub fn sum(vectors: List(Vec2f)) -> Vec2f {
vectors |> list.fold(vec2.splat(0.0), add)
}
/// Multiplies a list of vectors and returns the product.
///
/// ## Examples
///
/// ```gleam
/// assert
/// [Vec2(1.2, -3.4), Vec2(2.1, -1.0), Vec2(3.2, 2.0)]
/// |> product
/// == Vec2(8.064, 6.8)
/// ```
///
pub fn product(vectors: List(Vec2f)) -> Vec2f {
vectors |> list.fold(vec2.splat(1.0), multiply)
}
/// Returns the modulo of the inputs as a `Result`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(13.3, 13.3) |> modulo(Vec2(3.3, -3.3)) == Ok(Vec2(0.1, -3.2))
/// ```
///
/// ```gleam
/// assert Vec2(-13.3, -13.3) |> modulo(Vec2(3.3, -3.3)) == Ok(Vec2(3.2, -0.1))
/// ```
///
pub fn modulo(dividend: Vec2f, by divisor: Vec2f) -> Result(Vec2f, Nil) {
dividend |> vec2.map2(divisor, float.modulo) |> vec2.result
}
/// Returns division of the inputs as a `Result`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> divide(Vec2(2.0, 0.5)) == Ok(Vec2(0.6, -6.8))
/// ```
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> divide(Vec2(0.0, 0.5)) == Error(Nil)
/// ```
///
pub fn divide(dividend: Vec2f, by divisor: Vec2f) -> Result(Vec2f, Nil) {
dividend |> vec2.map2(divisor, float.divide) |> vec2.result
}
/// Adds two vectors together.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> add(Vec2(2.1, 4.5)) == Vec2(3.3, 1.1)
/// ```
///
pub fn add(a: Vec2f, b: Vec2f) -> Vec2f {
a |> vec2.map2(b, float.add)
}
/// Multiplies two vectors together.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> multiply(Vec2(2.1, -1.0)) == Vec2(2.52, 3.4)
/// ```
///
pub fn multiply(a: Vec2f, b: Vec2f) -> Vec2f {
a |> vec2.map2(b, float.multiply)
}
/// Subtracts one vector from another.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> subtract(Vec2(0.7, -4.5)) == Vec2(0.5, 1.1)
/// ```
///
pub fn subtract(a: Vec2f, b: Vec2f) -> Vec2f {
a |> vec2.map2(b, float.subtract)
}
/// Returns the squared length (squared magnitude) of the vector.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> length_squared == 13.0
/// ```
///
pub fn length_squared(vector: Vec2f) -> Float {
vector
|> vec2.to_list
|> list.map(fn(element) { element *. element })
|> float.sum
}
/// Returns the length (magnitude) of the vector.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> length == 3.61
/// ```
///
pub fn length(vector: Vec2f) -> Float {
let assert Ok(length) = vector |> length_squared |> float.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
/// assert compare_length(Vec2(1.2, -3.4), Vec2(1.0, 2.1)) == Gt
/// ```
///
pub fn compare_length(a: Vec2f, with b: Vec2f) -> Order {
float.compare(a |> length_squared, b |> length_squared)
}
/// Compares two vector's lengths within a tolerance, returning an `Order`:
/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.
///
/// ## Examples
///
/// ```gleam
/// assert
/// loosely_compare_length(
/// Vec2(1.2, -3.4),
/// Vec2(-1.25, 3.43),
/// tolerating: 0.5,
/// )
/// == Eq
/// ```
///
pub fn loosely_compare_length(
a: Vec2f,
with b: Vec2f,
tolerating tolerance: Float,
) -> Order {
float.loosely_compare(a |> length_squared, b |> length_squared, tolerance)
}
/// Returns the squared distance between two vectors.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> distance_squared(Vec2(1.0, 2.1)) == 30.29
/// ```
///
pub fn distance_squared(a: Vec2f, with b: Vec2f) -> Float {
a |> subtract(b) |> length_squared
}
/// Returns the distance between two vectors.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> distance(Vec2(1.0, 2.1)) == 5.5
/// ```
///
pub fn distance(a: Vec2f, with b: Vec2f) -> Float {
let assert Ok(distance) = distance_squared(a, b) |> float.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
/// assert
/// compare_distance(
/// Vec2(1.2, -3.4),
/// Vec2(1.0, 2.1),
/// Vec2(-2.5, 6.7),
/// )
/// == Gt
/// ```
///
pub fn compare_distance(a: Vec2f, with b: Vec2f, to vector: Vec2f) -> Order {
float.compare(a |> distance_squared(vector), b |> distance_squared(vector))
}
/// Compares two vector's distances to a vector within a tolerance, returning
/// an `Order`:
/// `Lt` for lower than, `Eq` for equals, or `Gt` for greater than.
///
/// ## Examples
///
/// ```gleam
/// assert
/// loosely_compare_distance(
/// Vec2(1.2, -3.4),
/// Vec2(1.25, -3.43),
/// Vec2(-2.5, 6.7),
/// tolerating: 1.0,
/// )
/// == Eq
/// ```
///
pub fn loosely_compare_distance(
a: Vec2f,
with b: Vec2f,
to vector: Vec2f,
tolerating tolerance: Float,
) -> Order {
float.loosely_compare(
a |> distance_squared(vector),
b |> distance_squared(vector),
tolerance,
)
}
/// Returns a new vector containing the elements multiplies by `scalar`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> scale(2.5) == Vec2(3.0, -8.5)
/// ```
///
pub fn scale(vector: Vec2f, by scalar: Float) -> Vec2f {
vector |> vec2.map(float.multiply(_, scalar))
}
/// Normalize the vector.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> normalize == Vec2(0.33, -0.94)
/// ```
///
pub fn normalize(vector: Vec2f) -> Vec2f {
vector |> scale(1.0 /. { vector |> length })
}
/// Returns a normalized vector pointing from `a` to `b`.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> direction(Vec2(1.0, 2.1)) == Vec2(-0.04, 1.0)
/// ```
///
pub fn direction(a: Vec2f, to b: Vec2f) -> Vec2f {
b |> subtract(a) |> normalize
}
/// Returns the cross product of two vectors.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> cross(Vec2(1.0, 2.1)) == 5.92
/// ```
///
pub fn cross(a: Vec2f, b: Vec2f) -> Float {
a.x *. b.y -. b.x *. a.y
}
/// Returns the dot product of two vectors.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> dot(Vec2(1.0, 2.1)) == -5.94
/// ```
///
pub fn dot(a: Vec2f, b: Vec2f) -> Float {
a |> multiply(b) |> vec2.to_list |> float.sum
}
/// Returns the projection of a vector on another vector.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> project(Vec2(1.0, 2.1)) == Vec2(-1.1, -2.31)
/// ```
///
pub fn project(a: Vec2f, on b: Vec2f) -> Vec2f {
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
/// assert Vec2(1.2, -3.4) |> slide(Vec2(1.0, 2.1)) == Vec2(2.3, -1.09)
/// ```
///
pub fn slide(a: Vec2f, on b: Vec2f) -> Vec2f {
a |> subtract(a |> project(b))
}
/// Returns the reflection of a vector through a plane defined by the given
/// normal vector.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> reflect(Vec2(1.0, 2.1)) == Vec2(-3.4, -1.21)
/// ```
///
pub fn reflect(vector: Vec2f, through normal: Vec2f) -> Vec2f {
vector |> project(normal) |> scale(2.0) |> subtract(vector)
}
/// Returns the mirror of a vector through a plane defined by the given normal
/// vector.
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> mirror(Vec2(1.0, 2.1)) == Vec2(3.34, 1.21)
/// ```
///
pub fn mirror(vector: Vec2f, through normal: Vec2f) -> Vec2f {
vector |> reflect(normal) |> negate
}
/// Returns the angle (in radians) between two vectors in the range of (0, π).
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> angle(Vec2(1.0, 2.1)) == 2.36
/// ```
///
pub fn angle(a: Vec2f, b: Vec2f) -> Float {
let assert Ok(angle) =
dot(normalize(a), normalize(b)) |> float.clamp(-1.0, 1.0) |> maths.acos
angle
}
/// Returns the signed angle (in radians) between two vectors in the range of
/// (-π, π).
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> signed_angle(Vec2(1.0, 2.1)) == -2.36
/// ```
///
pub fn signed_angle(a: Vec2f, b: Vec2f) -> Float {
maths.atan2(cross(b, a), dot(a, b))
}
/// Rotate a vector by an angle (in radians).
///
/// ## Examples
///
/// ```gleam
/// assert Vec2(1.2, -3.4) |> rotate(maths.pi() *. 0.25) == Vec2(3.25, -1.56)
/// ```
///
pub fn rotate(vector: Vec2f, by angle: Float) -> Vec2f {
let cos_angle = maths.cos(angle)
let sin_angle = maths.sin(angle)
Vec2(
vector.x *. cos_angle -. vector.y *. sin_angle,
vector.x *. sin_angle +. vector.y *. cos_angle,
)
}
/// Return the equivalent of `vector |> subtract(position) |> fun |> add(position)`.
///
/// ## Examples
///
/// ```gleam
/// assert
/// Vec2(1.2, -3.4)
/// |> anchor_position(Vec2(1.0, 2.1), rotate(_, maths.pi() *. 0.25))
/// == Vec2(5.03, -1.65)
/// ```
///
pub fn anchor_position(
vector: Vec2f,
at position: Vec2f,
then fun: fn(Vec2f) -> Vec2f,
) -> Vec2f {
vector |> subtract(position) |> fun |> add(position)
}
/// Return the equivalent of `vector |> rotate(float.negate(angle)) |> fun |> rotate(angle)`.
///
/// ## Examples
///
/// ```gleam
/// assert
/// Vec2(1.2, -3.4)
/// |> anchor_rotation(maths.pi() *. 0.25, add(_, Vec2(6.0, 9.0)))
/// == Vec2(-0.92, 7.21)
/// ```
///
pub fn anchor_rotation(
vector: Vec2f,
at angle: Float,
then fun: fn(Vec2f) -> Vec2f,
) -> Vec2f {
vector |> rotate(float.negate(angle)) |> fun |> rotate(angle)
}