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) }