Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,467 @@
|
||||
// Copyright(C) 2020-2024 Lars Pontoppidan. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license file distributed with this software package
|
||||
module vec
|
||||
|
||||
import math
|
||||
|
||||
pub const vec_epsilon = 10e-7
|
||||
|
||||
// Vec2[T] is a generic struct representing a vector in 2D space.
|
||||
pub struct Vec2[T] {
|
||||
pub mut:
|
||||
x T
|
||||
y T
|
||||
}
|
||||
|
||||
// vec2[T] returns a `Vec2` of type `T`, with `x` and `y` fields set.
|
||||
pub fn vec2[T](x T, y T) Vec2[T] {
|
||||
return Vec2[T]{
|
||||
x: x
|
||||
y: y
|
||||
}
|
||||
}
|
||||
|
||||
// zero sets the `x` and `y` fields to 0.
|
||||
pub fn (mut v Vec2[T]) zero() {
|
||||
v.x = 0
|
||||
v.y = 0
|
||||
}
|
||||
|
||||
// one sets the `x` and `y` fields to 1.
|
||||
pub fn (mut v Vec2[T]) one() {
|
||||
v.x = 1
|
||||
v.y = 1
|
||||
}
|
||||
|
||||
// copy returns a copy of this vector.
|
||||
pub fn (v Vec2[T]) copy() Vec2[T] {
|
||||
return Vec2[T]{v.x, v.y}
|
||||
}
|
||||
|
||||
// from sets the `x` and `y` fields from `u`.
|
||||
pub fn (mut v Vec2[T]) from(u Vec2[T]) {
|
||||
v.x = u.x
|
||||
v.y = u.y
|
||||
}
|
||||
|
||||
// from_vec3 sets the `x` and `y` fields from `u`.
|
||||
pub fn (mut v Vec2[T]) from_vec3[U](u Vec3[U]) {
|
||||
v.x = T(u.x)
|
||||
v.y = T(u.y)
|
||||
}
|
||||
|
||||
// as_vec3 returns a Vec3 with `x` and `y` fields set from `v`, `z` is set to 0.
|
||||
pub fn (v Vec2[T]) as_vec3[T]() Vec3[T] {
|
||||
return Vec3[T]{v.x, v.y, 0}
|
||||
}
|
||||
|
||||
// from_vec4 sets the `x` and `y` fields from `u`.
|
||||
pub fn (mut v Vec2[T]) from_vec4[U](u Vec4[U]) {
|
||||
v.x = T(u.x)
|
||||
v.y = T(u.y)
|
||||
}
|
||||
|
||||
// as_vec4 returns a Vec4 with `x` and `y` fields set from `v`, `z` and `w` is set to 0.
|
||||
pub fn (v Vec2[T]) as_vec4[T]() Vec4[T] {
|
||||
return Vec4[T]{v.x, v.y, 0, 0}
|
||||
}
|
||||
|
||||
//
|
||||
// Addition
|
||||
//
|
||||
|
||||
// + returns the resulting vector of the addition of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec2[T]) + (u Vec2[T]) Vec2[T] {
|
||||
return Vec2[T]{v.x + u.x, v.y + u.y}
|
||||
}
|
||||
|
||||
// add returns the resulting vector of the addition of `v` + `u`.
|
||||
pub fn (v Vec2[T]) add(u Vec2[T]) Vec2[T] {
|
||||
return v + u
|
||||
}
|
||||
|
||||
// add_scalar returns the resulting vector of the addition of the `scalar` value.
|
||||
pub fn (v Vec2[T]) add_scalar[U](scalar U) Vec2[T] {
|
||||
return Vec2[T]{v.x + T(scalar), v.y + T(scalar)}
|
||||
}
|
||||
|
||||
// plus adds vector `u` to the vector.
|
||||
pub fn (mut v Vec2[T]) plus(u Vec2[T]) {
|
||||
v.x += u.x
|
||||
v.y += u.y
|
||||
}
|
||||
|
||||
// plus_scalar adds the scalar `scalar` to the vector.
|
||||
pub fn (mut v Vec2[T]) plus_scalar[U](scalar U) {
|
||||
v.x += T(scalar)
|
||||
v.y += T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Subtraction
|
||||
//
|
||||
|
||||
// - returns the resulting vector of the subtraction of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec2[T]) - (u Vec2[T]) Vec2[T] {
|
||||
return Vec2[T]{v.x - u.x, v.y - u.y}
|
||||
}
|
||||
|
||||
// sub returns the resulting vector of the subtraction of `v` - `u`.
|
||||
pub fn (v Vec2[T]) sub(u Vec2[T]) Vec2[T] {
|
||||
return v - u
|
||||
}
|
||||
|
||||
// sub_scalar returns the resulting vector of the subtraction of the `scalar` value.
|
||||
pub fn (v Vec2[T]) sub_scalar[U](scalar U) Vec2[T] {
|
||||
return Vec2[T]{v.x - T(scalar), v.y - T(scalar)}
|
||||
}
|
||||
|
||||
// subtract subtracts vector `u` from the vector.
|
||||
pub fn (mut v Vec2[T]) subtract(u Vec2[T]) {
|
||||
v.x -= u.x
|
||||
v.y -= u.y
|
||||
}
|
||||
|
||||
// subtract_scalar subtracts the scalar `scalar` from the vector.
|
||||
pub fn (mut v Vec2[T]) subtract_scalar[U](scalar U) {
|
||||
v.x -= T(scalar)
|
||||
v.y -= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Multiplication
|
||||
//
|
||||
|
||||
// * returns the resulting vector of the multiplication of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec2[T]) * (u Vec2[T]) Vec2[T] {
|
||||
return Vec2[T]{v.x * u.x, v.y * u.y}
|
||||
}
|
||||
|
||||
// mul returns the resulting vector of the multiplication of `v` * `u`.
|
||||
pub fn (v Vec2[T]) mul(u Vec2[T]) Vec2[T] {
|
||||
return v * u
|
||||
}
|
||||
|
||||
// mul_scalar returns the resulting vector of the multiplication of the `scalar` value.
|
||||
pub fn (v Vec2[T]) mul_scalar[U](scalar U) Vec2[T] {
|
||||
return Vec2[T]{v.x * T(scalar), v.y * T(scalar)}
|
||||
}
|
||||
|
||||
// multiply multiplies the vector with `u`.
|
||||
pub fn (mut v Vec2[T]) multiply(u Vec2[T]) {
|
||||
v.x *= u.x
|
||||
v.y *= u.y
|
||||
}
|
||||
|
||||
// multiply_scalar multiplies the vector with `scalar`.
|
||||
pub fn (mut v Vec2[T]) multiply_scalar[U](scalar U) {
|
||||
v.x *= T(scalar)
|
||||
v.y *= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Division
|
||||
//
|
||||
|
||||
// / returns the resulting vector of the division of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec2[T]) / (u Vec2[T]) Vec2[T] {
|
||||
return Vec2[T]{v.x / u.x, v.y / u.y}
|
||||
}
|
||||
|
||||
// div returns the resulting vector of the division of `v` / `u`.
|
||||
pub fn (v Vec2[T]) div(u Vec2[T]) Vec2[T] {
|
||||
return v / u
|
||||
}
|
||||
|
||||
// div_scalar returns the resulting vector of the division by the `scalar` value.
|
||||
pub fn (v Vec2[T]) div_scalar[U](scalar U) Vec2[T] {
|
||||
return Vec2[T]{v.x / T(scalar), v.y / T(scalar)}
|
||||
}
|
||||
|
||||
// divide divides the vector by `u`.
|
||||
pub fn (mut v Vec2[T]) divide(u Vec2[T]) {
|
||||
v.x /= u.x
|
||||
v.y /= u.y
|
||||
}
|
||||
|
||||
// divide_scalar divides the vector by `scalar`.
|
||||
pub fn (mut v Vec2[T]) divide_scalar[U](scalar U) {
|
||||
v.x /= T(scalar)
|
||||
v.y /= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Utility
|
||||
//
|
||||
|
||||
// magnitude returns the magnitude, also known as the length, of the vector.
|
||||
pub fn (v Vec2[T]) magnitude() T {
|
||||
if v.x == 0 && v.y == 0 {
|
||||
return T(0)
|
||||
}
|
||||
$if T is f64 {
|
||||
return math.sqrt((v.x * v.x) + (v.y * v.y))
|
||||
} $else {
|
||||
return T(math.sqrt(f64(v.x * v.x) + f64(v.y * v.y)))
|
||||
}
|
||||
}
|
||||
|
||||
// magnitude_x returns the magnitude, also known as the length, of the 1D vector field x, y is ignored.
|
||||
pub fn (v Vec2[T]) magnitude_x() T {
|
||||
return T(math.sqrt(v.x * v.x))
|
||||
}
|
||||
|
||||
// magnitude_x returns the magnitude, also known as the length, of the 1D vector field y, x is ignored.
|
||||
pub fn (v Vec2[T]) magnitude_y() T {
|
||||
return T(math.sqrt(v.y * v.y))
|
||||
}
|
||||
|
||||
// dot returns the dot product of `v` and `u`.
|
||||
// The dot product is a scalar value that represents the magnitude of one vector
|
||||
// projected onto another vector.
|
||||
// It is calculated by multiplying the corresponding components of the vectors
|
||||
// and summing the results.
|
||||
// example:
|
||||
// ```v
|
||||
// v := vec2[f32](3, 4) //magnitude = 5
|
||||
// u := vec2[f32](5, 6) //magnitude = 7.81
|
||||
// dot := v.dot(u) // 3*5 + 4*6 = 15 + 24 = 39
|
||||
// (dot) // Output: 39
|
||||
// ```
|
||||
pub fn (v Vec2[T]) dot(u Vec2[T]) T {
|
||||
return (v.x * u.x) + (v.y * u.y)
|
||||
}
|
||||
|
||||
// cross returns the cross product of `v` and `u`.
|
||||
pub fn (v Vec2[T]) cross(u Vec2[T]) T {
|
||||
return (v.x * u.y) - (v.y * u.x)
|
||||
}
|
||||
|
||||
// unit returns the unit vector.
|
||||
// unit vectors always have a magnitude, or length, of exactly 1.
|
||||
pub fn (v Vec2[T]) unit() Vec2[T] {
|
||||
m := v.magnitude()
|
||||
return Vec2[T]{v.x / m, v.y / m}
|
||||
}
|
||||
|
||||
// perp_cw returns the clockwise, or "left-hand", perpendicular vector of this vector.
|
||||
pub fn (v Vec2[T]) perp_cw() Vec2[T] {
|
||||
return Vec2[T]{v.y, -v.x}
|
||||
}
|
||||
|
||||
// perp_ccw returns the counter-clockwise, or "right-hand", perpendicular vector of this vector.
|
||||
pub fn (v Vec2[T]) perp_ccw() Vec2[T] {
|
||||
return Vec2[T]{-v.y, v.x}
|
||||
}
|
||||
|
||||
// perpendicular returns the `v` projected perpendicular vector to the 'u' vector.
|
||||
pub fn (v Vec2[T]) perpendicular(u Vec2[T]) Vec2[T] {
|
||||
return v - v.project(u)
|
||||
}
|
||||
|
||||
// project returns the projected vector.
|
||||
// The projection of vector `v` onto vector `u` is the orthogonal projection
|
||||
// of `v` onto a straight line parallel to `u` that passes through the origin.
|
||||
// This is equivalent to the vector projection of `v` onto the unit vector in the direction of `u`.
|
||||
// and is given by the formula: proj_v(u) = (v · u / |u|^2) * u
|
||||
// where "·" denotes the dot product and |u| is the magnitude of vector `u`.
|
||||
// If `v` is a zero vector, the result will also be a zero vector.
|
||||
// example:
|
||||
// ```v
|
||||
// v := vec2[f32](3, 4)
|
||||
// u := vec2[f32](5, 6)
|
||||
// proj := v.project(u)
|
||||
// println(proj) // Output: vec2[f32](3.1967213, 3.8360658)
|
||||
// ```
|
||||
pub fn (v Vec2[T]) project(u Vec2[T]) Vec2[T] {
|
||||
scale := T(v.dot(u) / u.dot(u))
|
||||
return u.mul_scalar(scale)
|
||||
}
|
||||
|
||||
// rotate_around_cw returns the vector `v` rotated *clockwise* `radians` around an origin vector `o` in Cartesian space.
|
||||
pub fn (v Vec2[T]) rotate_around_cw(o Vec2[T], radians f64) Vec2[T] {
|
||||
return v.rotate_around_ccw(o, -radians)
|
||||
}
|
||||
|
||||
// rotate_around_ccw returns the vector `v` rotated *counter-clockwise* `radians` around an origin vector `o` in Cartesian space.
|
||||
pub fn (v Vec2[T]) rotate_around_ccw(o Vec2[T], radians f64) Vec2[T] {
|
||||
s := math.sin(radians)
|
||||
c := math.cos(radians)
|
||||
dx := v.x - o.x
|
||||
dy := v.y - o.y
|
||||
return Vec2[T]{
|
||||
x: T(c * dx - s * dy + o.x)
|
||||
y: T(s * dx + c * dy + o.y)
|
||||
}
|
||||
}
|
||||
|
||||
// eq returns a bool indicating if the two vectors are equal.
|
||||
@[inline]
|
||||
pub fn (v Vec2[T]) eq(u Vec2[T]) bool {
|
||||
return v.x == u.x && v.y == u.y
|
||||
}
|
||||
|
||||
// eq_epsilon returns a bool indicating if the two vectors are equal within the module `vec_epsilon` const.
|
||||
pub fn (v Vec2[T]) eq_epsilon(u Vec2[T]) bool {
|
||||
return v.eq_approx[T, T](u, T(vec_epsilon))
|
||||
}
|
||||
|
||||
// eq_approx returns whether these vectors are approximately equal within `tolerance`.
|
||||
pub fn (v Vec2[T]) eq_approx[T, U](u Vec2[T], tolerance U) bool {
|
||||
diff_x := math.abs(v.x - u.x)
|
||||
diff_y := math.abs(v.y - u.y)
|
||||
if diff_x <= tolerance && diff_y <= tolerance {
|
||||
return true
|
||||
}
|
||||
|
||||
max_x := math.max(math.abs(v.x), math.abs(u.x))
|
||||
max_y := math.max(math.abs(v.y), math.abs(u.y))
|
||||
if diff_x < max_x * tolerance && diff_y < max_y * tolerance {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// is_approx_zero returns whether this vector is equal to zero within `tolerance`.
|
||||
pub fn (v Vec2[T]) is_approx_zero(tolerance T) bool {
|
||||
if math.abs(v.x) <= tolerance && math.abs(v.y) <= tolerance {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// eq_scalar returns a bool indicating if the `x` and `y` fields both equals `scalar`.
|
||||
pub fn (v Vec2[T]) eq_scalar[U](scalar U) bool {
|
||||
return v.x == T(scalar) && v.y == scalar
|
||||
}
|
||||
|
||||
// distance returns the distance to the vector `u`.
|
||||
pub fn (v Vec2[T]) distance(u Vec2[T]) T {
|
||||
$if T is f64 {
|
||||
return math.sqrt((v.x - u.x) * (v.x - u.x) + (v.y - u.y) * (v.y - u.y))
|
||||
} $else {
|
||||
return T(math.sqrt(f64(v.x - u.x) * f64(v.x - u.x) + f64(v.y - u.y) * f64(v.y - u.y)))
|
||||
}
|
||||
}
|
||||
|
||||
// manhattan_distance returns the Manhattan Distance to the vector `u`.
|
||||
pub fn (v Vec2[T]) manhattan_distance(u Vec2[T]) T {
|
||||
return math.abs(v.x - u.x) + math.abs(v.y - u.y)
|
||||
}
|
||||
|
||||
// angle_between returns the angle in radians to the vector `u`.
|
||||
pub fn (v Vec2[T]) angle_between(u Vec2[T]) T {
|
||||
$if T is f64 {
|
||||
return math.atan2(v.cross(u), v.dot(u))
|
||||
} $else {
|
||||
return T(math.atan2(f64(v.cross(u)), f64(v.dot(u))))
|
||||
}
|
||||
}
|
||||
|
||||
// angle_towards returns the angle in radians between the horizontal axis,
|
||||
// and a line passing through the first and second point, as if the first point
|
||||
// was at the center of the coordinate system.
|
||||
pub fn (p1 Vec2[T]) angle_towards(p2 Vec2[T]) T {
|
||||
$if T is f64 {
|
||||
return math.atan2(p2.y - p1.y, p2.x - p1.x)
|
||||
} $else {
|
||||
return T(math.atan2(f64(p2.y) - f64(p1.y), f64(p2.x) - f64(p1.x)))
|
||||
}
|
||||
}
|
||||
|
||||
// angle returns the angle in radians of the vector.
|
||||
// example:
|
||||
// ```v
|
||||
// v := vec2[f32](3.0, 4.0)
|
||||
// a := v.angle()
|
||||
// assert a == 0.64 (approximate value in radians)
|
||||
// w := vec2[f32](0.0, 1.0)
|
||||
// b := w.angle()
|
||||
// assert b == 1.57 (approximate value in radians)
|
||||
// ```
|
||||
pub fn (v Vec2[T]) angle() T {
|
||||
$if T is f64 {
|
||||
return math.atan2(v.y, v.x)
|
||||
} $else {
|
||||
return T(math.atan2(f64(v.y), f64(v.x)))
|
||||
}
|
||||
}
|
||||
|
||||
// abs sets `x` and `y` field values to their absolute values.
|
||||
pub fn (mut v Vec2[T]) abs() {
|
||||
v.x = math.abs(v.x)
|
||||
v.y = math.abs(v.y)
|
||||
}
|
||||
|
||||
// clean returns a vector with all fields of this vector set to zero (0) if they fall within `tolerance`.
|
||||
pub fn (v Vec2[T]) clean[U](tolerance U) Vec2[T] {
|
||||
mut r := v.copy()
|
||||
if math.abs(v.x) < tolerance {
|
||||
r.x = 0
|
||||
}
|
||||
if math.abs(v.y) < tolerance {
|
||||
r.y = 0
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// clean_tolerance sets all fields to zero (0) if they fall within `tolerance`.
|
||||
pub fn (mut v Vec2[T]) clean_tolerance[U](tolerance U) {
|
||||
if math.abs(v.x) < tolerance {
|
||||
v.x = 0
|
||||
}
|
||||
if math.abs(v.y) < tolerance {
|
||||
v.y = 0
|
||||
}
|
||||
}
|
||||
|
||||
// inv returns the inverse, or reciprocal, of the vector.
|
||||
// If a field is zero, its inverse is also set to zero to avoid division by zero.
|
||||
// the direction the vector points is generally not preserved, but
|
||||
// the magnitude of each field is inverted.
|
||||
// example:
|
||||
// ```v
|
||||
// v := vec2[f32](2.0, 4.0)
|
||||
// inv_v := v.inv() // inv_v == vec2[f32](0.5, 0.25)
|
||||
// ```
|
||||
pub fn (v Vec2[T]) inv() Vec2[T] {
|
||||
return Vec2[T]{
|
||||
x: if v.x != 0 { T(1) / v.x } else { 0 }
|
||||
y: if v.y != 0 { T(1) / v.y } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// normalize normalizes the vector.
|
||||
// A normalized vector has the same direction as the original vector but a magnitude of 1.
|
||||
// If the vector has a magnitude of 0, a zero vector is returned since we cannot find the direction of a zero-length vector.
|
||||
// example:
|
||||
// ```v
|
||||
// v := vec2[f32](3.0, 4.0)//magnitude = 5.0
|
||||
// n := v.normalize() // n == vec2[f32](0.6, 0.8) // magnitude = 1.0
|
||||
// ```
|
||||
pub fn (v Vec2[T]) normalize() Vec2[T] {
|
||||
m := v.magnitude()
|
||||
if m == 0 {
|
||||
return vec2[T](0, 0)
|
||||
}
|
||||
return Vec2[T]{
|
||||
x: v.x * (1 / m)
|
||||
y: v.y * (1 / m)
|
||||
}
|
||||
}
|
||||
|
||||
// sum returns a sum of all the fields.
|
||||
// example:
|
||||
// ```v
|
||||
// v := vec2[f32](3.0, 4.0)
|
||||
// s := v.sum()
|
||||
// assert s == 7.0
|
||||
// ```
|
||||
pub fn (v Vec2[T]) sum() T {
|
||||
return v.x + v.y
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
import math { close, is_nan, radians, tolerance, veryclose }
|
||||
import math.vec
|
||||
|
||||
fn test_vec2_int() {
|
||||
mut v1 := vec.vec2(0, 0)
|
||||
mut v2 := vec.vec2(0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec2[int]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
}
|
||||
|
||||
fn test_vec2_f32() {
|
||||
mut v1 := vec.vec2(f32(0), 0)
|
||||
mut v2 := vec.vec2(f32(0), 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec2[f32]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
}
|
||||
|
||||
fn test_vec2_f64() {
|
||||
mut v1 := vec.vec2(0.0, 0)
|
||||
mut v2 := vec.vec2(0.0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec2[f64]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
}
|
||||
|
||||
fn test_vec2_f64_utils_1() {
|
||||
mut v1 := vec.vec2(2.0, 3.0)
|
||||
mut v2 := vec.vec2(1.0, 4.0)
|
||||
|
||||
mut zv := vec.vec2(5.0, 5.0)
|
||||
zv.zero()
|
||||
|
||||
v3 := v1 + v2
|
||||
assert v3.x == 3
|
||||
assert v3.y == 7
|
||||
|
||||
assert v1.dot(v2) == 14
|
||||
assert v1.cross(v2) == 5
|
||||
|
||||
v1l := vec.vec2(40.0, 9.0)
|
||||
assert v1l.magnitude() == 41
|
||||
|
||||
mut ctv1 := vec.vec2(0.000001, 0.000001)
|
||||
ctv1.clean_tolerance(0.00001)
|
||||
assert ctv1 == zv
|
||||
}
|
||||
|
||||
fn test_vec2_f64_utils_2() {
|
||||
mut v1 := vec.vec2(4.0, 4.0)
|
||||
assert veryclose(v1.unit().magnitude(), 1)
|
||||
v2 := v1.mul_scalar(0.5)
|
||||
assert v2.x == 2
|
||||
assert v2.y == 2
|
||||
assert veryclose(v2.unit().magnitude(), 1)
|
||||
|
||||
invv2 := v2.inv()
|
||||
assert invv2.x == 0.5
|
||||
assert invv2.y == 0.5
|
||||
}
|
||||
|
||||
fn fcorrect_angles() []f64 {
|
||||
return [
|
||||
math.pi * 1.0 / 4.0,
|
||||
math.pi * 2.0 / 4.0,
|
||||
math.pi * 3.0 / 4.0,
|
||||
math.pi,
|
||||
math.pi * 5.0 / 4.0 - 2 * math.pi,
|
||||
math.pi * 6.0 / 4.0 - 2 * math.pi,
|
||||
math.pi * 7.0 / 4.0 - 2 * math.pi,
|
||||
]
|
||||
}
|
||||
|
||||
fn fsurround() []vec.Vec2[f64] {
|
||||
return [
|
||||
vec.vec2(1.0, 1.0),
|
||||
vec.vec2(0.0, 1.0),
|
||||
vec.vec2(-1.0, 1.0),
|
||||
vec.vec2(-1.0, 0.0),
|
||||
vec.vec2(-1.0, -1.0),
|
||||
vec.vec2(0.0, -1.0),
|
||||
vec.vec2(1.0, -1.0),
|
||||
]
|
||||
}
|
||||
|
||||
fn test_vec2_angle_between() {
|
||||
surround := fsurround()
|
||||
correct_angles := fcorrect_angles()
|
||||
v1 := vec.vec2(1.0, 0.0)
|
||||
for i in 0 .. 7 {
|
||||
assert v1.angle_between(surround[i]) == correct_angles[i]
|
||||
// check the angle between non-normalised, scaled vectors too:
|
||||
assert v1.mul_scalar(5.0).angle_between(surround[i].mul_scalar(2.0)) == correct_angles[i]
|
||||
offset := f64(i) + 123.123456
|
||||
assert v1.mul_scalar(offset).angle_between(surround[i].mul_scalar(offset)) == correct_angles[i]
|
||||
// check f32 version:
|
||||
v1_f32 := vec.vec2(f32(v1.x), f32(v1.y))
|
||||
v2_f32 := vec.vec2(f32(surround[i].x), f32(surround[i].y))
|
||||
assert v1_f32.angle_between(v2_f32) == f32(correct_angles[i])
|
||||
// check int version:
|
||||
v1_int := vec.vec2(int(v1.x), int(v1.y))
|
||||
v2_int := vec.vec2(int(surround[i].x), int(surround[i].y))
|
||||
assert v1_int.angle_between(v2_int) == int(correct_angles[i])
|
||||
}
|
||||
}
|
||||
|
||||
fn test_vec2_angle_towards() {
|
||||
surround := fsurround()
|
||||
correct_angles := fcorrect_angles()
|
||||
// basic case, let p0 be the coordinate origin point:
|
||||
p0 := vec.vec2(0.0, 0.0)
|
||||
for i in 0 .. 7 {
|
||||
assert p0.angle_towards(surround[i]) == correct_angles[i]
|
||||
assert (surround[i] - p0).angle() == p0.angle_towards(surround[i])
|
||||
// check f32 version:
|
||||
p0_f32 := vec.vec2(f32(0), f32(0))
|
||||
s_f32 := vec.vec2(f32(surround[i].x), f32(surround[i].y))
|
||||
assert p0_f32.angle_towards(s_f32) == f32(correct_angles[i])
|
||||
// check int version:
|
||||
p0_int := vec.vec2(int(0), int(0))
|
||||
s_int := vec.vec2(int(surround[i].x), int(surround[i].y))
|
||||
assert p0_int.angle_towards(s_int) == int(correct_angles[i])
|
||||
// check invariants, when the 2 points are moved and translated:
|
||||
for x := -0.5; x <= 0.5; x += 0.1 {
|
||||
for y := -0.5; y <= 0.5; y += 0.1 {
|
||||
p1 := vec.vec2(x, y)
|
||||
p2 := p1.add(surround[i])
|
||||
assert veryclose(p1.angle_towards(p2), correct_angles[i]), 'i: ${i}, p1: ${p1} | p2: ${p2}'
|
||||
offset := f64(i) + 123.123456
|
||||
p1_o := p1.add_scalar(offset)
|
||||
p2_o := p2.add_scalar(offset)
|
||||
assert (p2_o - p1_o).angle() == p1_o.angle_towards(p2_o)
|
||||
assert close(p1_o.angle_towards(p2_o), correct_angles[i]), 'i: ${i}, p1_o: ${p1_o} | p2_o: ${p2_o}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_vec2_rotate_around_cw() {
|
||||
origin := vec.vec2(0.0, 0.0)
|
||||
mut v := vec.vec2(0.0, 1.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 1.0)
|
||||
assert close(v.y, 0.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 0.0)
|
||||
assert close(v.y, -1.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, -1.0)
|
||||
assert close(v.y, 0.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 0.0)
|
||||
assert close(v.y, 1.0)
|
||||
}
|
||||
|
||||
fn test_vec2_rotate_around_ccw() {
|
||||
origin := vec.vec2(0.0, 0.0)
|
||||
mut v := vec.vec2(0.0, 1.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, -1.0)
|
||||
assert close(v.y, 0.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, 0.0)
|
||||
assert close(v.y, -1.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, 1.0)
|
||||
assert close(v.y, 0.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, 0.0)
|
||||
assert close(v.y, 1.0)
|
||||
}
|
||||
|
||||
fn test_vec2_rotate_around_cw_2() {
|
||||
origin := vec.vec2(1.0, 1.0)
|
||||
mut v := vec.vec2(1.0, 2.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 2.0)
|
||||
assert close(v.y, 1.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 1.0)
|
||||
assert close(v.y, 0.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 0.0)
|
||||
assert close(v.y, 1.0)
|
||||
v = v.rotate_around_cw(origin, radians(90))
|
||||
assert close(v.x, 1.0)
|
||||
assert close(v.y, 2.0)
|
||||
}
|
||||
|
||||
fn test_vec2_rotate_around_ccw_2() {
|
||||
origin := vec.vec2(-1.0, 1.0)
|
||||
mut v := vec.vec2(-1.0, -1.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, 1.0)
|
||||
assert close(v.y, 1.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, -1.0)
|
||||
assert close(v.y, 3.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, -3.0)
|
||||
assert close(v.y, 1.0)
|
||||
v = v.rotate_around_ccw(origin, radians(90))
|
||||
assert close(v.x, -1.0)
|
||||
assert close(v.y, -1.0)
|
||||
}
|
||||
|
||||
// Test for Vec2 projection
|
||||
//
|
||||
fn test_vec2_project_onto_basic() {
|
||||
v := vec.vec2(5.0, 6.0) // magnitude ~7.81 vector
|
||||
u := vec.vec2(3.0, 4.0) // magnitude 5 vector
|
||||
// hand-computed:
|
||||
// v·u = 5*3 + 6*4 = 39
|
||||
// |u|^2 = 3^2 + 4^2 = 25
|
||||
// scale = 39/25 = 1.56
|
||||
// proj = scale * u = (1.56*3, 1.56*4) = (4.68, 6.24)
|
||||
proj := v.project(u)
|
||||
assert tolerance(proj.x, 4.68, vec.vec_epsilon)
|
||||
assert tolerance(proj.y, 6.24, vec.vec_epsilon)
|
||||
}
|
||||
|
||||
// Test for Vec2 projection onto zero vector
|
||||
// project v into the null vector
|
||||
fn test_vec2_project_onto_zero() {
|
||||
v := vec.vec2(5.0, 6.0)
|
||||
u := vec.vec2(0.0, 0.0)
|
||||
proj := v.project(u)
|
||||
// must be nan
|
||||
assert is_nan(proj.x)
|
||||
assert is_nan(proj.y)
|
||||
}
|
||||
|
||||
// Test for Vec2 projection of zero vector
|
||||
// project a null vector
|
||||
fn test_vec2_project_zero_vector() {
|
||||
v := vec.vec2(0.0, 0.0)
|
||||
u := vec.vec2(3.0, 4.0)
|
||||
proj := v.project(u)
|
||||
assert proj.x == 0.0
|
||||
assert proj.y == 0.0
|
||||
}
|
||||
|
||||
// Test for Vec2 projection onto itself
|
||||
//
|
||||
fn test_vec2_project_onto_self() {
|
||||
u := vec.vec2(3.0, 4.0)
|
||||
proj := u.project(u)
|
||||
assert veryclose(proj.x, u.x)
|
||||
assert veryclose(proj.y, u.y)
|
||||
}
|
||||
|
||||
// Test for Vec2 projection onto orthogonal vector
|
||||
//
|
||||
fn test_vec2_project_onto_orthogonal() {
|
||||
v := vec.vec2(0.0, 1.0)
|
||||
u := vec.vec2(1.0, 0.0)
|
||||
proj := u.project(v)
|
||||
// more sensitive to floating point errors so i think close is better here
|
||||
assert close(proj.x, 0.0)
|
||||
assert close(proj.y, 0.0)
|
||||
}
|
||||
|
||||
// Test for Vec2 projection with negative components
|
||||
//
|
||||
fn test_vec2_project_negative_components() {
|
||||
v := vec.vec2(5.0, -6.0)
|
||||
u := vec.vec2(-3.0, 4.0)
|
||||
// hand-computed:
|
||||
// v·u = 5*-3 + -6*4 = -15 - 24 = -39
|
||||
// |u|^2 = -3^2 + 4^2 = 9 + 16 = 25
|
||||
// scale = -39/25 = -1.56
|
||||
// proj = scale * u = (-1.56*-3, -1.56*4) = (4.68, -6.24)
|
||||
proj := v.project(u)
|
||||
assert tolerance(proj.x, 4.68, vec.vec_epsilon)
|
||||
assert tolerance(proj.y, -6.24, vec.vec_epsilon)
|
||||
}
|
||||
|
||||
// Test for perpendicularity
|
||||
// 'u' and 'v' are already perpendicular so it must return v
|
||||
fn test_vec2_perpendicularity_angle() {
|
||||
u := vec.vec2(1.0, 0.0)
|
||||
v := vec.vec2(0.0, 3.0)
|
||||
|
||||
per := v.perpendicular(u)
|
||||
assert tolerance(per.x, v.x, vec.vec_epsilon)
|
||||
assert tolerance(per.y, v.y, vec.vec_epsilon)
|
||||
}
|
||||
|
||||
// 'u' and 'v' are collinear so it must return the null vector
|
||||
fn test_vec2_collinear() {
|
||||
u := vec.vec2(1.0, 0.0)
|
||||
v := vec.vec2(3.0, 0.0)
|
||||
|
||||
per := v.perpendicular(u)
|
||||
assert tolerance(per.x, 0.0, vec.vec_epsilon)
|
||||
assert tolerance(per.y, 0.0, vec.vec_epsilon)
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
// Copyright(C) 2020-2024 Lars Pontoppidan. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license file distributed with this software package
|
||||
module vec
|
||||
|
||||
import math
|
||||
|
||||
// Vec3[T] is a generic struct representing a vector in 3D space.
|
||||
pub struct Vec3[T] {
|
||||
pub mut:
|
||||
x T
|
||||
y T
|
||||
z T
|
||||
}
|
||||
|
||||
// vec3[T] returns a `Vec3` of type `T`, with `x`,`y` and `z` fields set.
|
||||
pub fn vec3[T](x T, y T, z T) Vec3[T] {
|
||||
return Vec3[T]{
|
||||
x: x
|
||||
y: y
|
||||
z: z
|
||||
}
|
||||
}
|
||||
|
||||
// zero sets the `x`,`y` and `z` fields to 0.
|
||||
pub fn (mut v Vec3[T]) zero() {
|
||||
v.x = 0
|
||||
v.y = 0
|
||||
v.z = 0
|
||||
}
|
||||
|
||||
// one sets the `x`,`y` and `z` fields to 1.
|
||||
pub fn (mut v Vec3[T]) one() {
|
||||
v.x = 1
|
||||
v.y = 1
|
||||
v.z = 1
|
||||
}
|
||||
|
||||
// copy returns a copy of this vector.
|
||||
pub fn (mut v Vec3[T]) copy() Vec3[T] {
|
||||
return Vec3[T]{v.x, v.y, v.z}
|
||||
}
|
||||
|
||||
// from sets the `x`,`y` and `z` fields from `u`.
|
||||
pub fn (mut v Vec3[T]) from(u Vec3[T]) {
|
||||
v.x = u.x
|
||||
v.y = u.y
|
||||
v.z = u.z
|
||||
}
|
||||
|
||||
// from_vec2 sets the `x` and `y` fields from `u`.
|
||||
pub fn (mut v Vec3[T]) from_vec2[U](u Vec2[U]) {
|
||||
v.x = T(u.x)
|
||||
v.y = T(u.y)
|
||||
}
|
||||
|
||||
// as_vec2 returns a Vec2 with `x` and `y` fields set from `v`.
|
||||
pub fn (mut v Vec3[T]) as_vec2[T]() Vec2[T] {
|
||||
return Vec2[T]{v.x, v.y}
|
||||
}
|
||||
|
||||
// from_vec4 sets the `x`,`y` and `z` fields from `u`.
|
||||
pub fn (mut v Vec3[T]) from_vec4[U](u Vec4[U]) {
|
||||
v.x = T(u.x)
|
||||
v.y = T(u.y)
|
||||
v.z = T(u.z)
|
||||
}
|
||||
|
||||
// as_vec4 returns a Vec4 with `x`,`y` and `z` fields set from `v`, `w` is set to 0.
|
||||
pub fn (mut v Vec3[T]) as_vec4[T]() Vec4[T] {
|
||||
return Vec4[T]{v.x, v.y, v.z, 0}
|
||||
}
|
||||
|
||||
//
|
||||
// Addition
|
||||
//
|
||||
|
||||
// + returns the resulting vector of the addition of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) + (u Vec3[T]) Vec3[T] {
|
||||
return Vec3[T]{v.x + u.x, v.y + u.y, v.z + u.z}
|
||||
}
|
||||
|
||||
// add returns the resulting vector of the addition of `v` + `u`.
|
||||
pub fn (v Vec3[T]) add(u Vec3[T]) Vec3[T] {
|
||||
return v + u
|
||||
}
|
||||
|
||||
// add_vec2 returns the resulting vector of the addition of the
|
||||
// `x` and `y` fields of `u`, `z` is left untouched.
|
||||
pub fn (v Vec3[T]) add_vec2[U](u Vec2[U]) Vec3[T] {
|
||||
return Vec3[T]{v.x + T(u.x), v.y + T(u.y), v.z}
|
||||
}
|
||||
|
||||
// add_scalar returns the resulting vector of the addition of the `scalar` value.
|
||||
pub fn (v Vec3[T]) add_scalar[U](scalar U) Vec3[T] {
|
||||
return Vec3[T]{v.x + T(scalar), v.y + T(scalar), v.z + T(scalar)}
|
||||
}
|
||||
|
||||
// plus adds vector `u` to the vector.
|
||||
pub fn (mut v Vec3[T]) plus(u Vec3[T]) {
|
||||
v.x += u.x
|
||||
v.y += u.y
|
||||
v.z += u.z
|
||||
}
|
||||
|
||||
// plus_vec2 adds `x` and `y` fields of vector `u` to the vector, `z` is left untouched.
|
||||
pub fn (mut v Vec3[T]) plus_vec2[U](u Vec2[U]) {
|
||||
v.x += T(u.x)
|
||||
v.y += T(u.y)
|
||||
}
|
||||
|
||||
// plus_scalar adds the scalar `scalar` to the vector.
|
||||
pub fn (mut v Vec3[T]) plus_scalar[U](scalar U) {
|
||||
v.x += T(scalar)
|
||||
v.y += T(scalar)
|
||||
v.z += T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Subtraction
|
||||
//
|
||||
|
||||
// - returns the resulting vector of the subtraction of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) - (u Vec3[T]) Vec3[T] {
|
||||
return Vec3[T]{v.x - u.x, v.y - u.y, v.z - u.z}
|
||||
}
|
||||
|
||||
// sub returns the resulting vector of the subtraction of `v` - `u`.
|
||||
pub fn (v Vec3[T]) sub(u Vec3[T]) Vec3[T] {
|
||||
return v - u
|
||||
}
|
||||
|
||||
// sub_scalar returns the resulting vector of the subtraction of the `scalar` value.
|
||||
pub fn (v Vec3[T]) sub_scalar[U](scalar U) Vec3[T] {
|
||||
return Vec3[T]{v.x - T(scalar), v.y - T(scalar), v.z - T(scalar)}
|
||||
}
|
||||
|
||||
// subtract subtracts vector `u` from the vector.
|
||||
pub fn (mut v Vec3[T]) subtract(u Vec3[T]) {
|
||||
v.x -= u.x
|
||||
v.y -= u.y
|
||||
v.z -= u.z
|
||||
}
|
||||
|
||||
// subtract_scalar subtracts the scalar `scalar` from the vector.
|
||||
pub fn (mut v Vec3[T]) subtract_scalar[U](scalar U) {
|
||||
v.x -= T(scalar)
|
||||
v.y -= T(scalar)
|
||||
v.z -= T(scalar)
|
||||
}
|
||||
|
||||
// * returns the resulting vector of the multiplication of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) * (u Vec3[T]) Vec3[T] {
|
||||
return Vec3[T]{v.x * u.x, v.y * u.y, v.z * u.z}
|
||||
}
|
||||
|
||||
// mul returns the resulting vector of the multiplication of `v` * `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) mul(u Vec3[T]) Vec3[T] {
|
||||
return v * u
|
||||
}
|
||||
|
||||
// mul_scalar returns the resulting vector of the multiplication of the `scalar` value.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) mul_scalar[U](scalar U) Vec3[T] {
|
||||
return Vec3[T]{v.x * T(scalar), v.y * T(scalar), v.z * T(scalar)}
|
||||
}
|
||||
|
||||
// multiply multiplies the vector with `u`.
|
||||
pub fn (mut v Vec3[T]) multiply(u Vec3[T]) {
|
||||
v.x *= u.x
|
||||
v.y *= u.y
|
||||
v.z *= u.z
|
||||
}
|
||||
|
||||
// multiply_scalar multiplies the vector with `scalar`.
|
||||
pub fn (mut v Vec3[T]) multiply_scalar[U](scalar U) {
|
||||
v.x *= T(scalar)
|
||||
v.y *= T(scalar)
|
||||
v.z *= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Division
|
||||
//
|
||||
|
||||
// / returns the resulting vector of the division of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) / (u Vec3[T]) Vec3[T] {
|
||||
return Vec3[T]{v.x / u.x, v.y / u.y, v.z / u.z}
|
||||
}
|
||||
|
||||
// div returns the resulting vector of the division of `v` / `u`.
|
||||
pub fn (v Vec3[T]) div(u Vec3[T]) Vec3[T] {
|
||||
return v / u
|
||||
}
|
||||
|
||||
// div_scalar returns the resulting vector of the division by the `scalar` value.
|
||||
pub fn (v Vec3[T]) div_scalar[U](scalar U) Vec3[T] {
|
||||
return Vec3[T]{v.x / T(scalar), v.y / T(scalar), v.z / T(scalar)}
|
||||
}
|
||||
|
||||
// divide divides the vector by `u`.
|
||||
pub fn (mut v Vec3[T]) divide(u Vec3[T]) {
|
||||
v.x /= u.x
|
||||
v.y /= u.y
|
||||
v.z /= u.z
|
||||
}
|
||||
|
||||
// divide_scalar divides the vector by `scalar`.
|
||||
pub fn (mut v Vec3[T]) divide_scalar[U](scalar U) {
|
||||
v.x /= T(scalar)
|
||||
v.y /= T(scalar)
|
||||
v.z /= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Utility
|
||||
//
|
||||
|
||||
// magnitude returns the magnitude, also known as the length, of the vector.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) magnitude() T {
|
||||
if v.x == 0 && v.y == 0 && v.z == 0 {
|
||||
return T(0)
|
||||
}
|
||||
return T(math.sqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z)))
|
||||
}
|
||||
|
||||
// dot returns the dot product of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) dot(u Vec3[T]) T {
|
||||
return T((v.x * u.x) + (v.y * u.y) + (v.z * u.z))
|
||||
}
|
||||
|
||||
// cross returns the cross product of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) cross(u Vec3[T]) Vec3[T] {
|
||||
return Vec3[T]{
|
||||
x: (v.y * u.z) - (v.z * u.y)
|
||||
y: (v.z * u.x) - (v.x * u.z)
|
||||
z: (v.x * u.y) - (v.y * u.x)
|
||||
}
|
||||
}
|
||||
|
||||
// unit returns the unit vector. unit vectors always have a magnitude, or length, of exactly 1.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) unit() Vec3[T] {
|
||||
m := v.magnitude()
|
||||
invm := 1 / m
|
||||
return Vec3[T]{v.x * invm, v.y * invm, v.z * invm}
|
||||
}
|
||||
|
||||
// perpendicular returns the `v` projected perpendicular vector to the 'u' vector.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) perpendicular(u Vec3[T]) Vec3[T] {
|
||||
return v - v.project(u)
|
||||
}
|
||||
|
||||
// project returns the projected vector.
|
||||
// The projection of vector `v` onto vector `u` is the orthogonal projection
|
||||
// of `v` onto a straight line parallel to `u` that passes through the origin.
|
||||
// This is equivalent to the vector projection of `v` onto the unit vector in the direction of `u`.
|
||||
// and is given by the formula: proj_v(u) = (v · u / |u|^2) * u
|
||||
// where "·" denotes the dot product and |u| is the magnitude of vector `u`.
|
||||
// If `v` is a zero vector, the result will also be a zero vector.
|
||||
// example:
|
||||
// TODO: add examples
|
||||
pub fn (v Vec3[T]) project(u Vec3[T]) Vec3[T] {
|
||||
scale := T(v.dot(u) / u.dot(u))
|
||||
return u.mul_scalar(scale)
|
||||
}
|
||||
|
||||
// eq returns a bool indicating if the two vectors are equal.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) eq(u Vec3[T]) bool {
|
||||
return v.x == u.x && v.y == u.y && v.z == u.z
|
||||
}
|
||||
|
||||
// eq_epsilon returns a bool indicating if the two vectors are equal within the module `vec_epsilon` const.
|
||||
pub fn (v Vec3[T]) eq_epsilon(u Vec3[T]) bool {
|
||||
return v.eq_approx[T, T](u, T(vec_epsilon))
|
||||
}
|
||||
|
||||
// eq_approx returns whether these vectors are approximately equal within `tolerance`.
|
||||
pub fn (v Vec3[T]) eq_approx[T, U](u Vec3[T], tolerance U) bool {
|
||||
diff_x := math.abs(v.x - u.x)
|
||||
diff_y := math.abs(v.y - u.y)
|
||||
diff_z := math.abs(v.z - u.z)
|
||||
if diff_x <= tolerance && diff_y <= tolerance && diff_z <= tolerance {
|
||||
return true
|
||||
}
|
||||
|
||||
max_x := math.max(math.abs(v.x), math.abs(u.x))
|
||||
max_y := math.max(math.abs(v.y), math.abs(u.y))
|
||||
max_z := math.max(math.abs(v.z), math.abs(u.z))
|
||||
if diff_x < max_x * tolerance && diff_y < max_y * tolerance && diff_z < max_z * tolerance {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// is_approx_zero returns whether this vector is equal to zero within `tolerance`.
|
||||
pub fn (v Vec3[T]) is_approx_zero(tolerance f64) bool {
|
||||
if math.abs(v.x) <= tolerance && math.abs(v.y) <= tolerance && math.abs(v.z) <= tolerance {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// eq_scalar returns a bool indicating if the `x`,`y` and `z` fields all equals `scalar`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) eq_scalar[U](scalar U) bool {
|
||||
return v.x == T(scalar) && v.y == T(scalar) && v.z == T(scalar)
|
||||
}
|
||||
|
||||
// distance returns the distance to the vector `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) distance(u Vec3[T]) f64 {
|
||||
return math.sqrt((v.x - u.x) * (v.x - u.x) + (v.y - u.y) * (v.y - u.y) +
|
||||
(v.z - u.z) * (v.z - u.z))
|
||||
}
|
||||
|
||||
// manhattan_distance returns the Manhattan distance to the vector `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) manhattan_distance(u Vec3[T]) f64 {
|
||||
return math.abs(v.x - u.x) + math.abs(v.y - u.y) + math.abs(v.z - u.z)
|
||||
}
|
||||
|
||||
// angle_between returns the angle in radians to the vector `u`.
|
||||
pub fn (v Vec3[T]) angle_between(u Vec3[T]) T {
|
||||
$if T is f64 {
|
||||
return math.acos(((v.x * u.x) + (v.y * u.y) + (v.z * u.z)) / math.sqrt((v.x * v.x) +
|
||||
(v.y * v.y) + (v.z * v.z)) * math.sqrt((u.x * u.x) + (u.y * u.y) + (u.z * u.z)))
|
||||
} $else {
|
||||
return T(math.acos(f64((v.x * u.x) + (v.y * u.y) + (v.z * u.z)) / math.sqrt(
|
||||
f64(v.x * v.x) + (v.y * v.y) + (v.z * v.z)) * math.sqrt(f64(u.x * u.x) + (u.y * u.y) +
|
||||
(u.z * u.z))))
|
||||
}
|
||||
}
|
||||
|
||||
// abs sets `x`, `y` and `z` field values to their absolute values.
|
||||
pub fn (mut v Vec3[T]) abs() {
|
||||
if v.x < 0 {
|
||||
v.x = math.abs(v.x)
|
||||
}
|
||||
if v.y < 0 {
|
||||
v.y = math.abs(v.y)
|
||||
}
|
||||
if v.z < 0 {
|
||||
v.z = math.abs(v.z)
|
||||
}
|
||||
}
|
||||
|
||||
// clean returns a vector with all fields of this vector set to zero (0) if they fall within `tolerance`.
|
||||
pub fn (v Vec3[T]) clean[U](tolerance U) Vec3[T] {
|
||||
mut r := v.copy()
|
||||
if math.abs(v.x) < tolerance {
|
||||
r.x = 0
|
||||
}
|
||||
if math.abs(v.y) < tolerance {
|
||||
r.y = 0
|
||||
}
|
||||
if math.abs(v.z) < tolerance {
|
||||
r.z = 0
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// clean_tolerance sets all fields to zero (0) if they fall within `tolerance`.
|
||||
pub fn (mut v Vec3[T]) clean_tolerance[U](tolerance U) {
|
||||
if math.abs(v.x) < tolerance {
|
||||
v.x = 0
|
||||
}
|
||||
if math.abs(v.y) < tolerance {
|
||||
v.y = 0
|
||||
}
|
||||
if math.abs(v.z) < tolerance {
|
||||
v.z = 0
|
||||
}
|
||||
}
|
||||
|
||||
// inv returns the inverse, or reciprocal, of the vector.
|
||||
pub fn (v Vec3[T]) inv() Vec3[T] {
|
||||
return Vec3[T]{
|
||||
x: if v.x != 0 { T(1) / v.x } else { 0 }
|
||||
y: if v.y != 0 { T(1) / v.y } else { 0 }
|
||||
z: if v.z != 0 { T(1) / v.z } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// normalize normalizes the vector.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) normalize() Vec3[T] {
|
||||
m := v.magnitude()
|
||||
if m == 0 {
|
||||
return vec3[T](0, 0, 0)
|
||||
}
|
||||
return Vec3[T]{
|
||||
x: v.x / m
|
||||
y: v.y / m
|
||||
z: v.z / m
|
||||
}
|
||||
}
|
||||
|
||||
// sum returns a sum of all the fields.
|
||||
@[inline]
|
||||
pub fn (v Vec3[T]) sum() T {
|
||||
return v.x + v.y + v.z
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import math { tolerance, veryclose }
|
||||
import math.vec
|
||||
|
||||
fn test_vec3_int() {
|
||||
mut v1 := vec.vec3(0, 0, 0)
|
||||
mut v2 := vec.vec3(0, 0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1.z == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec3[int]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
assert v3.z == 2
|
||||
}
|
||||
|
||||
fn test_vec3_f32() {
|
||||
mut v1 := vec.vec3(f32(0), 0, 0)
|
||||
mut v2 := vec.vec3(f32(0), 0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1.z == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec3[f32]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
assert v3.z == 2
|
||||
}
|
||||
|
||||
fn test_vec3_f64() {
|
||||
mut v1 := vec.vec3(0.0, 0, 0)
|
||||
mut v2 := vec.vec3(0.0, 0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1.z == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec3[f64]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
assert v3.z == 2
|
||||
}
|
||||
|
||||
fn test_vec3_f64_utils_1() {
|
||||
mut v1 := vec.vec3(2.0, 3.0, 1.5)
|
||||
mut v2 := vec.vec3(1.0, 4.0, 1.5)
|
||||
|
||||
mut zv := vec.vec3(5.0, 5.0, 5.0)
|
||||
zv.zero()
|
||||
|
||||
v3 := v1 + v2
|
||||
assert v3.x == 3
|
||||
assert v3.y == 7
|
||||
assert v3.z == 3
|
||||
|
||||
v1l := vec.vec3(6.0, 2.0, -3.0)
|
||||
assert v1l.magnitude() == 7
|
||||
|
||||
mut ctv1 := vec.vec3(0.000001, 0.000001, 0.000001)
|
||||
ctv1.clean_tolerance(0.00001)
|
||||
assert ctv1 == zv
|
||||
}
|
||||
|
||||
fn test_vec3_f64_utils_2() {
|
||||
mut v1 := vec.vec3(4.0, 4.0, 8.0)
|
||||
assert v1.unit().magnitude() == 1
|
||||
v2 := v1.mul_scalar(0.5)
|
||||
assert v2.x == 2
|
||||
assert v2.y == 2
|
||||
assert v2.z == 4
|
||||
assert v2.unit().magnitude() == 1
|
||||
|
||||
invv2 := v2.inv()
|
||||
assert invv2.x == 0.5
|
||||
assert invv2.y == 0.5
|
||||
assert invv2.z == 0.25
|
||||
}
|
||||
|
||||
// sample tests for vec3 projection
|
||||
fn test_vec3_project_onto_basic() {
|
||||
v := vec.vec3(5.0, 6.0, 0.0) // magnitude ~7.81 vector
|
||||
u := vec.vec3(3.0, 4.0, 0.0) // magnitude 5 vector
|
||||
// hand-computed:
|
||||
// v·u = 5*3 + 6*4 + 0*0 = 39
|
||||
// |u|^2 = 3^2 + 4^2 +0^2 = 25
|
||||
// scale = 39/25 = 1.56
|
||||
// proj = scale * u = (1.56*3, 1.56*4, 1.56*0) = (4.68, 6.24, 0)
|
||||
proj := v.project(u)
|
||||
assert veryclose(proj.x, 4.68)
|
||||
assert veryclose(proj.y, 6.24)
|
||||
assert veryclose(proj.z, 0.0)
|
||||
}
|
||||
|
||||
// Test for Vec3 projection onto zero vector
|
||||
//
|
||||
fn test_vec3_project_onto_zero() {
|
||||
v := vec.vec3(0.0, 0.0, 0.0)
|
||||
u := vec.vec3(3.0, 4.0, 0.0)
|
||||
proj := v.project(u)
|
||||
assert proj.x == 0.0
|
||||
assert proj.y == 0.0
|
||||
assert proj.z == 0.0
|
||||
}
|
||||
|
||||
// Test for vec3 projection at an angle
|
||||
//
|
||||
fn test_vec3_project_onto_angle() {
|
||||
v := vec.vec3(1.0, 1.0, 0.0) // magnitude sqrt(2) vector
|
||||
u := vec.vec3(1.0, 0.0, 0.0) // magnitude 1 vector
|
||||
// hand-computed:
|
||||
// v·u = 1*1 + 1*0 + 0*0 = 1
|
||||
// |u|^2 = 1^2 + 0^2 +0^2 = 1
|
||||
// scale = 1/1 = 1
|
||||
// proj = scale * u = (1*1, 1*0, 1*0) = (1, 0, 0)
|
||||
proj := v.project(u)
|
||||
assert veryclose(proj.x, 1.0)
|
||||
assert veryclose(proj.y, 0.0)
|
||||
assert veryclose(proj.z, 0.0)
|
||||
}
|
||||
|
||||
// Test for perpendicularity
|
||||
// 'u' and 'v' are already perpendicular so it must return v
|
||||
fn test_vec3_perpendicularity_angle() {
|
||||
u := vec.vec3(1.0, 0.0, 0.0)
|
||||
v := vec.vec3(0.0, 3.0, 2.0)
|
||||
|
||||
per := v.perpendicular(u)
|
||||
assert tolerance(per.x, v.x, vec.vec_epsilon)
|
||||
assert tolerance(per.y, v.y, vec.vec_epsilon)
|
||||
assert tolerance(per.z, v.z, vec.vec_epsilon)
|
||||
}
|
||||
|
||||
// 'u' and 'v' are collinear so the result must be the null vector
|
||||
fn test_vec3_collinear() {
|
||||
u := vec.vec3(1.0, 0.0, 0.0)
|
||||
v := vec.vec3(3.0, 0.0, 0.0)
|
||||
|
||||
per := v.perpendicular(u)
|
||||
assert tolerance(per.x, 0.0, vec.vec_epsilon)
|
||||
assert tolerance(per.y, 0.0, vec.vec_epsilon)
|
||||
assert tolerance(per.z, 0.0, vec.vec_epsilon)
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
// Copyright(C) 2020-2024 Lars Pontoppidan. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license file distributed with this software package
|
||||
module vec
|
||||
|
||||
import math
|
||||
|
||||
// Vec4[T] is a generic struct representing a vector in 4D space.
|
||||
pub struct Vec4[T] {
|
||||
pub mut:
|
||||
x T
|
||||
y T
|
||||
z T
|
||||
w T
|
||||
}
|
||||
|
||||
// vec4[T] returns a `Vec4` of type `T`, with `x`,`y`,`z` and `w` fields set.
|
||||
pub fn vec4[T](x T, y T, z T, w T) Vec4[T] {
|
||||
return Vec4[T]{
|
||||
x: x
|
||||
y: y
|
||||
z: z
|
||||
w: w
|
||||
}
|
||||
}
|
||||
|
||||
// zero sets the `x`,`y`,`z` and `w` fields to 0.
|
||||
pub fn (mut v Vec4[T]) zero() {
|
||||
v.x = 0
|
||||
v.y = 0
|
||||
v.z = 0
|
||||
v.w = 0
|
||||
}
|
||||
|
||||
// one sets the `x`,`y`,`z` and `w` fields to 1.
|
||||
pub fn (mut v Vec4[T]) one() {
|
||||
v.x = 1
|
||||
v.y = 1
|
||||
v.z = 1
|
||||
v.w = 1
|
||||
}
|
||||
|
||||
// copy returns a copy of this vector.
|
||||
pub fn (v Vec4[T]) copy() Vec4[T] {
|
||||
return Vec4[T]{v.x, v.y, v.z, v.w}
|
||||
}
|
||||
|
||||
// from sets the `x`,`y`,`z` and `w` fields from `u`.
|
||||
pub fn (mut v Vec4[T]) from(u Vec4[T]) {
|
||||
v.x = u.x
|
||||
v.y = u.y
|
||||
v.z = u.z
|
||||
v.w = u.w
|
||||
}
|
||||
|
||||
// from_vec2 sets the `x` and `y` fields from `u`.
|
||||
pub fn (mut v Vec4[T]) from_vec2(u Vec2[T]) {
|
||||
v.x = u.x
|
||||
v.y = u.y
|
||||
}
|
||||
|
||||
// as_vec2 returns a Vec2 with `x` and `y` fields set from `v`.
|
||||
pub fn (v Vec4[T]) as_vec2[U]() Vec2[U] {
|
||||
return Vec2[U]{v.x, v.y}
|
||||
}
|
||||
|
||||
// from_vec3 sets the `x`,`y` and `z` fields from `u`.
|
||||
pub fn (mut v Vec4[T]) from_vec3[U](u Vec3[U]) {
|
||||
v.x = T(u.x)
|
||||
v.y = T(u.y)
|
||||
v.z = T(u.z)
|
||||
}
|
||||
|
||||
// as_vec3 returns a Vec3 with `x`,`y` and `z` fields set from `v`.
|
||||
pub fn (v Vec4[T]) as_vec3[U]() Vec3[U] {
|
||||
return Vec3[U]{v.x, v.y, v.z}
|
||||
}
|
||||
|
||||
//
|
||||
// Addition
|
||||
//
|
||||
|
||||
// + returns the resulting vector of the addition of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec4[T]) + (u Vec4[T]) Vec4[T] {
|
||||
return Vec4[T]{v.x + u.x, v.y + u.y, v.z + u.z, v.w + u.w}
|
||||
}
|
||||
|
||||
// add returns the resulting vector of the addition of `v` + `u`.
|
||||
pub fn (v Vec4[T]) add(u Vec4[T]) Vec4[T] {
|
||||
return v + u
|
||||
}
|
||||
|
||||
// add_vec2 returns the resulting vector of the addition of the
|
||||
// `x` and `y` fields of `u`, `z` is left untouched.
|
||||
pub fn (v Vec4[T]) add_vec2[U](u Vec2[U]) Vec4[T] {
|
||||
return Vec4[T]{v.x + u.x, v.y + u.y, 0, 0}
|
||||
}
|
||||
|
||||
// add_vec3 returns the resulting vector of the addition of the
|
||||
// `x`,`y` and `z` fields of `u`, `w` is left untouched.
|
||||
pub fn (v Vec4[T]) add_vec3[U](u Vec3[U]) Vec4[T] {
|
||||
return Vec4[T]{v.x + u.x, v.y + u.y, v.z + u.z, 0}
|
||||
}
|
||||
|
||||
// add_scalar returns the resulting vector of the addition of the `scalar` value.
|
||||
pub fn (v Vec4[T]) add_scalar[U](scalar U) Vec4[T] {
|
||||
return Vec4[T]{v.x + T(scalar), v.y + T(scalar), v.z + T(scalar), v.w + T(scalar)}
|
||||
}
|
||||
|
||||
// plus adds vector `u` to the vector.
|
||||
pub fn (mut v Vec4[T]) plus(u Vec4[T]) {
|
||||
v.x += u.x
|
||||
v.y += u.y
|
||||
v.z += u.z
|
||||
v.w += u.w
|
||||
}
|
||||
|
||||
// plus_scalar adds the scalar `scalar` to the vector.
|
||||
pub fn (mut v Vec4[T]) plus_scalar[U](scalar U) {
|
||||
v.x += T(scalar)
|
||||
v.y += T(scalar)
|
||||
v.z += T(scalar)
|
||||
v.w += T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Subtraction
|
||||
//
|
||||
|
||||
// - returns the resulting vector of the subtraction of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec4[T]) - (u Vec4[T]) Vec4[T] {
|
||||
return Vec4[T]{v.x - u.x, v.y - u.y, v.z - u.z, v.w - u.w}
|
||||
}
|
||||
|
||||
// sub returns the resulting vector of the subtraction of `v` - `u`.
|
||||
pub fn (v Vec4[T]) sub(u Vec4[T]) Vec4[T] {
|
||||
return v - u
|
||||
}
|
||||
|
||||
// sub_scalar returns the resulting vector of the subtraction of the `scalar` value.
|
||||
pub fn (v Vec4[T]) sub_scalar[U](scalar U) Vec4[T] {
|
||||
return Vec4[T]{v.x - T(scalar), v.y - T(scalar), v.z - T(scalar), v.w - T(scalar)}
|
||||
}
|
||||
|
||||
// subtract subtracts vector `u` from the vector.
|
||||
pub fn (mut v Vec4[T]) subtract(u Vec4[T]) {
|
||||
v.x -= u.x
|
||||
v.y -= u.y
|
||||
v.z -= u.z
|
||||
v.w -= u.w
|
||||
}
|
||||
|
||||
// subtract_scalar subtracts the scalar `scalar` from the vector.
|
||||
pub fn (mut v Vec4[T]) subtract_scalar[U](scalar U) {
|
||||
v.x -= T(scalar)
|
||||
v.y -= T(scalar)
|
||||
v.z -= T(scalar)
|
||||
v.w -= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Multiplication
|
||||
//
|
||||
|
||||
// * returns the resulting vector of the multiplication of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec4[T]) * (u Vec4[T]) Vec4[T] {
|
||||
return Vec4[T]{v.x * u.x, v.y * u.y, v.z * u.z, v.w * u.w}
|
||||
}
|
||||
|
||||
// mul returns the resulting vector of the multiplication of `v` * `u`.
|
||||
pub fn (v Vec4[T]) mul(u Vec4[T]) Vec4[T] {
|
||||
return v * u
|
||||
}
|
||||
|
||||
// mul_scalar returns the resulting vector of the multiplication of the `scalar` value.
|
||||
pub fn (v Vec4[T]) mul_scalar[U](scalar U) Vec4[T] {
|
||||
return Vec4[T]{v.x * T(scalar), v.y * T(scalar), v.z * T(scalar), v.w * T(scalar)}
|
||||
}
|
||||
|
||||
// multiply multiplies the vector with `u`.
|
||||
pub fn (mut v Vec4[T]) multiply(u Vec4[T]) {
|
||||
v.x *= u.x
|
||||
v.y *= u.y
|
||||
v.z *= u.z
|
||||
v.w *= u.w
|
||||
}
|
||||
|
||||
// multiply_scalar multiplies the vector with `scalar`.
|
||||
pub fn (mut v Vec4[T]) multiply_scalar[U](scalar U) {
|
||||
v.x *= T(scalar)
|
||||
v.y *= T(scalar)
|
||||
v.z *= T(scalar)
|
||||
v.w *= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Division
|
||||
//
|
||||
|
||||
// / returns the resulting vector of the division of `v` and `u`.
|
||||
@[inline]
|
||||
pub fn (v Vec4[T]) / (u Vec4[T]) Vec4[T] {
|
||||
return Vec4[T]{v.x / u.x, v.y / u.y, v.z / u.z, v.w / u.w}
|
||||
}
|
||||
|
||||
// div returns the resulting vector of the division of `v` / `u`.
|
||||
pub fn (v Vec4[T]) div(u Vec4[T]) Vec4[T] {
|
||||
return v / u
|
||||
}
|
||||
|
||||
// div_scalar returns the resulting vector of the division by the `scalar` value.
|
||||
pub fn (v Vec4[T]) div_scalar[U](scalar U) Vec4[T] {
|
||||
return Vec4[T]{v.x / T(scalar), v.y / T(scalar), v.z / T(scalar), v.w / T(scalar)}
|
||||
}
|
||||
|
||||
// divide divides the vector by `u`.
|
||||
pub fn (mut v Vec4[T]) divide(u Vec4[T]) {
|
||||
v.x /= u.x
|
||||
v.y /= u.y
|
||||
v.z /= u.z
|
||||
v.w /= u.w
|
||||
}
|
||||
|
||||
// divide_scalar divides the vector by `scalar`.
|
||||
pub fn (mut v Vec4[T]) divide_scalar[U](scalar U) {
|
||||
v.x /= T(scalar)
|
||||
v.y /= T(scalar)
|
||||
v.z /= T(scalar)
|
||||
v.w /= T(scalar)
|
||||
}
|
||||
|
||||
//
|
||||
// Utility
|
||||
//
|
||||
|
||||
// magnitude returns the magnitude, also known as the length, of the vector.
|
||||
pub fn (v Vec4[T]) magnitude() T {
|
||||
if v.x == 0 && v.y == 0 && v.z == 0 && v.w == 0 {
|
||||
return T(0)
|
||||
}
|
||||
return T(math.sqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z) + (v.w * v.w)))
|
||||
}
|
||||
|
||||
// dot returns the dot product of `v` and `u`.
|
||||
pub fn (v Vec4[T]) dot(u Vec4[T]) T {
|
||||
return T((v.x * u.x) + (v.y * u.y) + (v.z * u.z) + (v.w * u.w))
|
||||
}
|
||||
|
||||
// cross_xyz returns the cross product of `v` and `u`'s `x`,`y` and `z` fields.
|
||||
pub fn (v Vec4[T]) cross_xyz(u Vec4[T]) Vec4[T] {
|
||||
return Vec4[T]{
|
||||
x: (v.y * u.z) - (v.z * u.y)
|
||||
y: (v.z * u.x) - (v.x * u.z)
|
||||
z: (v.x * u.y) - (v.y * u.x)
|
||||
w: 0
|
||||
}
|
||||
}
|
||||
|
||||
// unit returns the unit vector.
|
||||
// unit vectors always have a magnitude, or length, of exactly 1.
|
||||
pub fn (v Vec4[T]) unit() Vec4[T] {
|
||||
m := v.magnitude()
|
||||
return Vec4[T]{
|
||||
x: v.x / m
|
||||
y: v.y / m
|
||||
z: v.z / m
|
||||
w: v.w / m
|
||||
}
|
||||
}
|
||||
|
||||
// perpendicular returns the `v` projected perpendicular vector to the 'u' vector.
|
||||
pub fn (v Vec4[T]) perpendicular(u Vec4[T]) Vec4[T] {
|
||||
return v - v.project(u)
|
||||
}
|
||||
|
||||
// project returns the projected vector.
|
||||
// The projection of vector `v` onto vector `u` is the orthogonal projection
|
||||
// of `v` onto a straight line parallel to `u` that passes through the origin.
|
||||
// This is equivalent to the vector projection of `v` onto the unit vector in the direction of `u`.
|
||||
// and is given by the formula: proj_v(u) = (v · u / |u|^2) * u
|
||||
// where "·" denotes the dot product and |u| is the magnitude of vector `u`.
|
||||
// If `v` is a zero vector, the result will also be a zero vector.
|
||||
// example:
|
||||
// TODO: add examples
|
||||
pub fn (v Vec4[T]) project(u Vec4[T]) Vec4[T] {
|
||||
scale := T(v.dot(u) / u.dot(u))
|
||||
return u.mul_scalar(scale)
|
||||
}
|
||||
|
||||
// eq returns a bool indicating if the two vectors are equal.
|
||||
@[inline]
|
||||
pub fn (v Vec4[T]) eq(u Vec4[T]) bool {
|
||||
return v.x == u.x && v.y == u.y && v.z == u.z && v.w == u.w
|
||||
}
|
||||
|
||||
// eq_epsilon returns a bool indicating if the two vectors are equal within the module `vec_epsilon` const.
|
||||
pub fn (v Vec4[T]) eq_epsilon(u Vec4[T]) bool {
|
||||
return v.eq_approx[T, T](u, T(vec_epsilon))
|
||||
}
|
||||
|
||||
// eq_approx returns whether these vectors are approximately equal within `tolerance`.
|
||||
pub fn (v Vec4[T]) eq_approx[T, U](u Vec4[T], tolerance U) bool {
|
||||
diff_x := math.abs(v.x - u.x)
|
||||
diff_y := math.abs(v.y - u.y)
|
||||
diff_z := math.abs(v.z - u.z)
|
||||
diff_w := math.abs(v.w - u.w)
|
||||
if diff_x <= tolerance && diff_y <= tolerance && diff_z <= tolerance && diff_w <= tolerance {
|
||||
return true
|
||||
}
|
||||
|
||||
max_x := math.max(math.abs(v.x), math.abs(u.x))
|
||||
max_y := math.max(math.abs(v.y), math.abs(u.y))
|
||||
max_z := math.max(math.abs(v.z), math.abs(u.z))
|
||||
max_w := math.max(math.abs(v.w), math.abs(u.w))
|
||||
if diff_x < max_x * tolerance && diff_y < max_y * tolerance && diff_z < max_z * tolerance
|
||||
&& diff_w < max_w * tolerance {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// is_approx_zero returns whether this vector is equal to zero within `tolerance`.
|
||||
pub fn (v Vec4[T]) is_approx_zero(tolerance f64) bool {
|
||||
if math.abs(v.x) <= tolerance && math.abs(v.y) <= tolerance && math.abs(v.z) <= tolerance
|
||||
&& math.abs(v.w) <= tolerance {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// eq_scalar returns a bool indicating if the `x`,`y`,`z` and `w` fields all equals `scalar`.
|
||||
pub fn (v Vec4[T]) eq_scalar[U](scalar U) bool {
|
||||
return v.x == scalar && v.y == T(scalar) && v.z == T(scalar) && v.w == T(scalar)
|
||||
}
|
||||
|
||||
// distance returns the distance to the vector `u`.
|
||||
pub fn (v Vec4[T]) distance(u Vec4[T]) f64 {
|
||||
return math.sqrt((v.x - u.x) * (v.x - u.x) + (v.y - u.y) * (v.y - u.y) +
|
||||
(v.z - u.z) * (v.z - u.z) + (v.w - u.w) * (v.w - u.w))
|
||||
}
|
||||
|
||||
// manhattan_distance returns the Manhattan distance to the vector `u`.
|
||||
pub fn (v Vec4[T]) manhattan_distance(u Vec4[T]) f64 {
|
||||
return math.abs(v.x - u.x) + math.abs(v.y - u.y) + math.abs(v.z - u.z) + math.abs(v.w - u.w)
|
||||
}
|
||||
|
||||
// abs sets `x`, `y`, `z` and `w` field values to their absolute values.
|
||||
pub fn (mut v Vec4[T]) abs() {
|
||||
if v.x < 0 {
|
||||
v.x = math.abs(v.x)
|
||||
}
|
||||
if v.y < 0 {
|
||||
v.y = math.abs(v.y)
|
||||
}
|
||||
if v.z < 0 {
|
||||
v.z = math.abs(v.z)
|
||||
}
|
||||
if v.w < 0 {
|
||||
v.w = math.abs(v.w)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE a few of the following functions was adapted and/or inspired from Dario Deleddas excellent
|
||||
// work on the `gg.m4` vlib module. Here's the Copyright/license text covering that code:
|
||||
//
|
||||
// Copyright (c) 2021 Dario Deledda. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// clean returns a vector with all fields of this vector set to zero (0) if they fall within `tolerance`.
|
||||
pub fn (v Vec4[T]) clean[U](tolerance U) Vec4[T] {
|
||||
mut r := v.copy()
|
||||
if math.abs(v.x) < tolerance {
|
||||
r.x = 0
|
||||
}
|
||||
if math.abs(v.y) < tolerance {
|
||||
r.y = 0
|
||||
}
|
||||
if math.abs(v.z) < tolerance {
|
||||
r.z = 0
|
||||
}
|
||||
if math.abs(v.w) < tolerance {
|
||||
r.w = 0
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// clean_tolerance sets all fields to zero (0) if they fall within `tolerance`.
|
||||
pub fn (mut v Vec4[T]) clean_tolerance[U](tolerance U) {
|
||||
if math.abs(v.x) < tolerance {
|
||||
v.x = 0
|
||||
}
|
||||
if math.abs(v.y) < tolerance {
|
||||
v.y = 0
|
||||
}
|
||||
if math.abs(v.z) < tolerance {
|
||||
v.z = 0
|
||||
}
|
||||
if math.abs(v.w) < tolerance {
|
||||
v.w = 0
|
||||
}
|
||||
}
|
||||
|
||||
// inv returns the inverse, or reciprocal, of the vector.
|
||||
pub fn (v Vec4[T]) inv() Vec4[T] {
|
||||
return Vec4[T]{
|
||||
x: if v.x != 0 { T(1) / v.x } else { 0 }
|
||||
y: if v.y != 0 { T(1) / v.y } else { 0 }
|
||||
z: if v.z != 0 { T(1) / v.z } else { 0 }
|
||||
w: if v.w != 0 { T(1) / v.w } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// normalize normalizes the vector.
|
||||
pub fn (v Vec4[T]) normalize() Vec4[T] {
|
||||
m := v.magnitude()
|
||||
if m == 0 {
|
||||
return vec4[T](0, 0, 0, 0)
|
||||
}
|
||||
return Vec4[T]{
|
||||
x: v.x * (1 / m)
|
||||
y: v.y * (1 / m)
|
||||
z: v.z * (1 / m)
|
||||
w: v.w * (1 / m)
|
||||
}
|
||||
}
|
||||
|
||||
// sum returns a sum of all the fields.
|
||||
pub fn (v Vec4[T]) sum() T {
|
||||
return v.x + v.y + v.z + v.w
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import math { tolerance }
|
||||
import math.vec
|
||||
|
||||
fn test_vec4_int() {
|
||||
mut v1 := vec.vec4(0, 0, 0, 0)
|
||||
mut v2 := vec.vec4(0, 0, 0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1.z == 1
|
||||
assert v1.w == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec4[int]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
assert v3.z == 2
|
||||
assert v3.w == 2
|
||||
}
|
||||
|
||||
fn test_vec4_f32() {
|
||||
mut v1 := vec.vec4(f32(0), 0, 0, 0)
|
||||
mut v2 := vec.vec4(f32(0), 0, 0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1.z == 1
|
||||
assert v1.w == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec4[f32]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
assert v3.z == 2
|
||||
assert v3.w == 2
|
||||
}
|
||||
|
||||
fn test_vec4_f64() {
|
||||
mut v1 := vec.vec4(0.0, 0, 0, 0)
|
||||
mut v2 := vec.vec4(0.0, 0, 0, 0)
|
||||
assert v1 == v2
|
||||
v1.one()
|
||||
v2.one()
|
||||
assert v1.x == 1
|
||||
assert v1.y == 1
|
||||
assert v1.z == 1
|
||||
assert v1.w == 1
|
||||
assert v1 == v2
|
||||
|
||||
v3 := v1 + v2
|
||||
assert typeof(v3).name == 'vec.Vec4[f64]'
|
||||
assert v3.x == 2
|
||||
assert v3.y == 2
|
||||
assert v3.z == 2
|
||||
assert v3.w == 2
|
||||
}
|
||||
|
||||
fn test_vec4_f64_utils_1() {
|
||||
mut v1 := vec.vec4(2.0, 3.0, 1.5, 3.0)
|
||||
mut v2 := vec.vec4(1.0, 4.0, 1.5, 3.0)
|
||||
|
||||
mut zv := vec.vec4(5.0, 5.0, 5.0, 5.0)
|
||||
zv.zero()
|
||||
|
||||
v3 := v1 + v2
|
||||
assert v3.x == 3
|
||||
assert v3.y == 7
|
||||
assert v3.z == 3
|
||||
assert v3.w == 6
|
||||
|
||||
assert v3.unit().magnitude() == 1
|
||||
|
||||
mut ctv1 := vec.vec4(0.000001, 0.000001, 0.000001, 0.000001)
|
||||
ctv1.clean_tolerance(0.00001)
|
||||
assert ctv1 == zv
|
||||
}
|
||||
|
||||
fn test_vec4_f64_utils_2() {
|
||||
mut v1 := vec.vec4(4.0, 4.0, 8.0, 2.0)
|
||||
assert v1.unit().magnitude() == 1
|
||||
|
||||
v2 := v1.mul_scalar(0.5)
|
||||
assert v2.x == 2
|
||||
assert v2.y == 2
|
||||
assert v2.z == 4
|
||||
assert v2.w == 1
|
||||
assert v2.unit().magnitude() == 1
|
||||
|
||||
invv2 := v2.inv()
|
||||
assert invv2.x == 0.5
|
||||
assert invv2.y == 0.5
|
||||
assert invv2.z == 0.25
|
||||
assert invv2.w == 1.0
|
||||
}
|
||||
|
||||
// sample tests for vec4 projection
|
||||
fn test_vec4_project_onto_basic() {
|
||||
v := vec.vec4(5.0, 6.0, 0.0, 0.0) // magnitude ~7.81 vector
|
||||
u := vec.vec4(3.0, 4.0, 0.0, 0.0) // magnitude 5 vector
|
||||
// hand-computed:
|
||||
// v·u = 5*3 + 6*4 + 0*0 + 0*0 = 39
|
||||
// |u|^2 = 3^2 + 4^2 +0^2 +0^2 = 25
|
||||
proj := v.project(u)
|
||||
assert proj.x == 4.68
|
||||
assert proj.y == 6.24
|
||||
assert proj.z == 0.0
|
||||
assert proj.w == 0.0
|
||||
}
|
||||
|
||||
// Test for Vec4 projection onto zero vector
|
||||
//
|
||||
fn test_vec4_project_onto_zero() {
|
||||
v := vec.vec4(0.0, 0.0, 0.0, 0.0)
|
||||
u := vec.vec4(3.0, 4.0, 0.0, 0.0)
|
||||
proj := v.project(u)
|
||||
assert proj.x == 0.0
|
||||
assert proj.y == 0.0
|
||||
assert proj.z == 0.0
|
||||
assert proj.w == 0.0
|
||||
}
|
||||
|
||||
// Test for perpendicularity
|
||||
// 'u' and 'v' are already perpendicular so it must return v
|
||||
fn test_vec4_perpendicularity_angle() {
|
||||
u := vec.vec4(1.0, 0.0, 0.0, 0.0)
|
||||
v := vec.vec4(0.0, 3.0, 2.0, 0.0)
|
||||
|
||||
per := v.perpendicular(u)
|
||||
assert tolerance(per.x, v.x, vec.vec_epsilon)
|
||||
assert tolerance(per.y, v.y, vec.vec_epsilon)
|
||||
assert tolerance(per.z, v.z, vec.vec_epsilon)
|
||||
assert tolerance(per.w, v.w, vec.vec_epsilon)
|
||||
}
|
||||
|
||||
// 'u' and 'v' are collinear so it must return the null vector
|
||||
fn test_vec4_collinear() {
|
||||
u := vec.vec4(1.0, 0.0, 0.0, 0.0)
|
||||
v := vec.vec4(3.0, 0.0, 0.0, 0.0)
|
||||
|
||||
per := v.perpendicular(u)
|
||||
assert tolerance(per.x, 0.0, vec.vec_epsilon)
|
||||
assert tolerance(per.y, 0.0, vec.vec_epsilon)
|
||||
assert tolerance(per.z, 0.0, vec.vec_epsilon)
|
||||
assert tolerance(per.w, 0.0, vec.vec_epsilon)
|
||||
}
|
||||
Reference in new issue
Block a user