#module_parameters( UNITS: enum { radians; degrees; turns; } = .turns, RUN_TESTS := false ); // @todo(judah): dumb we can't use meta.range_for here. U8_Min, U8_Max :: #run meta.lo_for(u8), #run meta.hi_for(u8); U16_Min, U16_Max :: #run meta.lo_for(u16), #run meta.hi_for(u16); U32_Min, U32_Max :: #run meta.lo_for(u32), #run meta.hi_for(u32); U64_Min, U64_Max :: #run meta.lo_for(u64), #run meta.hi_for(u64); S8_Min, S8_Max :: #run meta.lo_for(s8), #run meta.hi_for(s8); S16_Min, S16_Max :: #run meta.lo_for(s16), #run meta.hi_for(s16); S32_Min, S32_Max :: #run meta.lo_for(s32), #run meta.hi_for(s32); S64_Min, S64_Max :: #run meta.lo_for(s64), #run meta.hi_for(s64); F32_Min, F32_Max :: #run meta.lo_for(float32), #run meta.hi_for(float32); F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64); #load "vec.jai"; #load "mat.jai"; #load "ease.jai"; #load "common.jai"; #scope_module; #if RUN_TESTS #run { test :: #import "jc/test"; vec2_tests(); vec3_tests(); vec4_tests(); vecn_tests(); mat2_tests(); mat4_tests(); quat_tests(); } #scope_file; meta :: #import "jc/meta"; basic :: #import "Basic"; // @future math :: #import "Math"; test :: #import "jc/test"; vec2_tests :: () { dot_print("Vec2 % tests", UNITS); t: test.T; { a: Vec2 = v2f(0.0, 1.0); b: Vec2 = v2f(1.0, 2.0); test.expect(*t, a + b == v2f(1.0, 3.0)); test.expect(*t, b - a == v2f(1.0, 1.0)); test.expect(*t, a*b == v2f(0.0, 2.0)); test.expect(*t, a/b == v2f(0.0, 0.5)); } { a: Vec(2, int) = v2i(2, 1); b: Vec(2, int) = v2i(1, 0); test.expect(*t, a + b == v2i(3, 1)); test.expect(*t, b - a == v2i(-1, -1)); test.expect(*t, a*b == v2i(2, 0)); test.expect(*t, b/a == v2i(0, 0)); } { a: Vec2 = v2f(2.3, -4.1); b: Vec2 = v2f(1.0, 3.6); c := min(a, b); test.expect(*t, c == v2f(1.0, -4.1)); c = max(a, b); test.expect(*t, c == v2f(2.3, 3.6)); } } vec3_tests :: () { dot_print("Vec3 % tests", UNITS); t: test.T; { a: Vec3 = v3f(0.0, 1.0, 2.0); b: Vec3 = v3f(1.0, 2.0, 3.0); test.expect(*t, a + b == v3f(1.0, 3.0, 5.0)); test.expect(*t, b - a == v3f(1.0, 1.0, 1.0)); test.expect(*t, a*b == v3f(0.0, 2.0, 6.0)); test.expect(*t, a/b == v3f(0.0, 0.5, 0.66666667)); a = v3f(1.0, 1.0, 0.0); b = v3f(1.0, 0.0, 0.0); test.expect(*t, reflect(a, b) == v3f(1.0, -1.0, 0.0)); test.expect(*t, round(v3f(1.2, 1.7, 1.5)) == v3f(1.0, 2.0, 2.0)); test.expect(*t, round(v3f(-1.2, -1.7, -1.5)) == v3f(-1.0, -2.0, -2.0)); a = v3f(1.0, 0.0, 0.0); b = v3f(0.0, 1.0, 0.0); test.expect(*t, cross(a, b) == v3f(0.0, 0.0, 1.0)); } { a: Vec3 = v3f(2.3, 4.1, 9.0); b: Vec3 = v3f(1.0, -3.6, 5.0); c := min(a, b); test.expect(*t, c == v3f(1.0, -3.6, 5.0)); c = max(a, b); test.expect(*t, c == v3f(2.3, 4.1, 9.0)); } } vec4_tests :: () { dot_print("Vec4 % tests", UNITS); t: test.T; { a: Vec4 = v4f(2.25, 1.0, 2.0, 1.0); b: Vec4 = v4f(4.0, 2.0, 3.0, 1.0); test.expect(*t, a + b == v4f(6.25, 3.0, 5.0, 2.0)); test.expect(*t, b - a == v4f(1.75, 1.0, 1.0, 0.0)); test.expect(*t, a*b == v4f(9.0, 2.0, 6.0, 1.0)); test.expect(*t, a/b == v4f(0.5625, 0.5, 2.0/3.0, 1.0)); } } vecn_tests :: () { dot_print("VecN % tests", UNITS); t: test.T; { a: Vec(16, float); b: Vec(16, float); for *a { it.* = xx it_index; } for *b { it.* = xx(it_index + 1); } test.expect(*t, a + b == Vec(16, float).{.[1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0]}); test.expect(*t, b - a == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]}); test.expect(*t, a*b == Vec(16, float).{.[0.0, 2.0, 6.0, 12.0, 20.0, 30.0, 42.0, 56.0, 72.0, 90.0, 110.0, 132.0, 156.0, 182.0, 210.0, 240.0]}); test.expect(*t, min(a, b) == a); test.expect(*t, max(a, b) == b); test.expect(*t, max(a, 12) == Vec(16, float).{.[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 12.0, 12.0, 12.0]}); test.expect(*t, min(a, 13.2) == Vec(16, float).{.[13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 13.2, 14.0, 15.0]}); test.expect(*t, clamp(a, 7.25, 12.0) == Vec(16, float).{.[7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 7.25, 8, 9, 10, 11, 12, 12, 12, 12]}); a1: Vec(16, float) = Vec(16, float).{.[1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 4.7, -1.2, -1.0, -1.5, 11.2, 14.0, 15.0, 14.0, 15.0, 65536.2]}; basic.print(">> a1: %\n", a1); basic.print(">> ceil(a1): %\n", ceil(a1)); basic.print(">> floor(a1): %\n", floor(a1)); test.expect(*t, ceil(a1) == Vec(16, float).{.[2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, -1.0, -1.0, -1.0, 12.0, 14.0, 15.0, 14.0, 15.0, 65537]}); test.expect(*t, floor(a1) == Vec(16, float).{.[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 4.0, -2.0, -1.0, -2.0, 11.0, 14.0, 15.0, 14.0, 15.0, 65536]}); test.expect(*t, dot(a, b) == 1360.0); test.expect(*t, abs(a) == a); c := a; for *c { // Check making every other component negative if it_index%2 == 0 it.* = -it.*; } test.expect(*t, abs(c) == a); test.expect(*t, float_eq(length(normalize(a)), 1)); test.expect(*t, a + 2 == Vec(16, float).{.[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}); test.expect(*t, a - 2 == Vec(16, float).{.[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}); test.expect(*t, a*2 == Vec(16, float).{.[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]}); } } mat2_tests :: () { dot_print("Mat2 % tests", UNITS); t: test.T; { identity := m2_identity; a: Mat2 = Mat2.{.[1, 2, 3, 4]}; test.expect(*t, a*identity == a); } { basic.print("rotation matrix\n"); rotator := rotation_mat2(from_rad(PI)); v2 := v2f(1.0, 0.5); basic.print("rotating: %\n", v2); v2 = rotator*v2; basic.print("after rotate: %\n", v2); test.expect(*t, v2_eq(v2, v2f(-1.0, -0.5))); v2 = rotator*v2; basic.print("rotate back: %\n", v2); test.expect(*t, v2_eq(v2, v2f(1.0, 0.5))); } { basic.print("determinate\n"); m := m2(1.0, 3.0, 2.0, 2.0); basic.print("m2: %\n", m); det := determinate(m); basic.print("determinate: %\n", det); test.expect(*t, det == -4); } { basic.print("inverse test\n"); rotator := rotation_mat2(from_rad(PI)); inv_rotator := inverse(rotator); v2 := v2f(0.1, 1.0); basic.print(" : %\n", rotator); basic.print("inv: %\n", inv_rotator); basic.print("inv_mat*mat = %\n", inv_rotator*rotator); test.expect(*t, inv_rotator*rotator == m2_identity); test.expect(*t, inv_rotator*(rotator*v2) == v2); basic.print("v2 : %\n", v2); v2 = rotator*v2; basic.print("rot : %\n", v2); v2 = inv_rotator*v2; basic.print("inv : %\n", v2); } } mat4_tests :: () { dot_print("Mat4 % tests", UNITS); t: test.T; { identity := m4_identity; a: Mat4 = Mat4.{.[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}; test.expect(*t, a*identity == a); } { UP_VECTOR :: Vec3.{.[0.0, 1.0, 0.0]}; camera := v3f(1.0, 0.0, 1.0); looking_at := v3f(0.0, 0.0, 0.0); look_at := make_look_at(camera, looking_at, UP_VECTOR); basic.print("lookat matrix: %\n", look_at); basic.print("lookat from 0,1,0: %\n", look_at*v4f(0.0, 1.0, 0.0, 1.0)); } { translator := translate(v3f(10.0, 5.0, 2.0)); v3 := v3f(1.0, 2.0, 1.0); v4 := v4f(v3, 1.0); test.expect(*t, v4 == v4f(1.0, 2.0, 1.0, 1.0)); test.expect(*t, translator*v4 == v4f(11.0, 7.0, 3.0, 1.0)); } { basic.print("rotation matrix\n"); rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); v4 := v4f(1.0, 0.5, 0.1, 1.0); basic.print("rotating: %\n", v4); v4 = rotator*v4; basic.print("after rotate: %\n", v4); test.expect(*t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0))); v4 = rotator*v4; basic.print("rotate back: %\n", v4); test.expect(*t, v4_eq(v4, v4f(1.0, 0.5, 0.1, 1.0))); } { rotator_x := rotation_mat4(v3f(1.0, 0.0, 0.0), from_rad(0.4*PI)); rotator_y := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); camera_rotator := rotator_x*rotator_y; basic.print("big rotator: %\n", camera_rotator); det := determinate(camera_rotator); basic.print("determinate: %\n", det); } { basic.print("inverse test\n"); rotator := rotation_mat4(v3f(0.0, 0.0, 1.0), from_rad(PI)); inv_rotator := inverse(rotator); v4 := v4f(0.1, 1.0, 0.0, 1.0); basic.print(" : %\n", rotator); basic.print("inv: %\n", inv_rotator); basic.print("inv_mat*mat = %\n", inv_rotator*rotator); test.expect(*t, inv_rotator*rotator == m4_identity); basic.print("v4 : %\n", v4); v4 = rotator*v4; basic.print("rot : %\n", v4); v4 = inv_rotator*v4; basic.print("inv : %\n", v4); } } quat_tests :: () { dot_print("Quaternion % tests", UNITS); t: test.T; { qi := quat_identity; basic.print("qi lensq: %\n", length_squared(qi)); basic.print("qi len : %\n", length(qi)); q: Quat = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); q2: Quat = rotation_quat(from_rad(PI), v3f(1.0, 0.0, 1.0)); basic.print("q : %\n", q); basic.print("q2 : %\n", q2); basic.print("dot : %\n", dot(q, q2)); basic.print("mul : %\n", q*q2); qc := conjugate(q); basic.print("conjugate : %\n", qc); inv_q := inverse(q); basic.print("inverse : %\n", inv_q); test.expect(*t, q*inv_q == qi); q1 := quat(2, 0, 0, 0); q2 = quat(1, 1, -1, 0); basic.print("q1: %\n", q1); basic.print("q2: %\n", q2); basic.print("q1*q2: %\n", q1*q2); basic.print("dot(q2,q2): %\n", dot(q2, q2)); basic.print("dot(q1,q2): %\n", dot(q1, q2)); basic.print("q1*q2*conj(q1): %\n", q1*q2*conjugate(q1)); c := q1*q2*conjugate(q1); test.expect(*t, float_eq(c.w, 0.0)); q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); p := v3f(2.0, 0.0, 0.0); basic.print("q: %\n", q); basic.print("p: %\n", p); c1 := q*quat(p, 0)*conjugate(q); basic.print("q*quat(p, 0)*conjugate(q): %\n", c1); c2 := q*p; basic.print("q*p : %\n", c2); test.expect(*t, v3_eq(c2, v3f(math.sqrt(2.0), math.sqrt(2.0), 0.0))); test.expect(*t, v3_eq(c1.xyz, c2)); basic.print("quaternion to matrix rotation\n"); q = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); basic.print("q: %\n", q); m := rotation_mat4(q); print_mat4(m); p1 := v4f(2.0, 0.0, 0.0, 1.0); basic.print("p : %\n", p1); basic.print("p': %\n", m*p1); test.expect(*t, v4_eq(m*p1, v4f(-2.0, 0.0, -0.0, 1.0))); q1 = rotation_quat(from_rad(PI), v3f(0.0, 1.0, 0.0)); q2 = rotation_quat(from_rad(2.0*PI), v3f(0.0, 1.0, 0.0)); perc := 0.5; q = slerp(q1, q2, perc); basic.print("q1: %\n", q1); basic.print("q2: %\n", q2); basic.print("slerp q1, q2, %: %\n", perc, q); q = rotation_quat(from_rad(PI/4.0), v3f(0.0, 0.0, 1.0)); print_mat4(rotation_mat4(q)); } } dot_print :: (fmt: string, args: ..Any, width: int = 30) { dots := "................................................................."; str := basic.tprint(fmt, args); dots.count = width - str.count; basic.print("%0%", str, dots); basic.print("\n"); } check :: (cond: bool, loc := #caller_location) -> bool { if !cond { basic.print("\tTest failed at %:%\n", loc.fully_pathed_filename, loc.line_number); } return cond; } v2_eq :: (a: Vec2, b: Vec2) -> bool { return float_eq(a.x, b.x) && float_eq(a.y, b.y); } v3_eq :: (a: Vec3, b: Vec3) -> bool { return float_eq(a.x, b.x) && float_eq(a.y, b.y) && float_eq(a.z, b.z); } v4_eq :: (a: Vec4, b: Vec4) -> bool { return float_eq(a.x, b.x) && float_eq(a.y, b.y) && float_eq(a.z, b.z) && float_eq(a.w, b.w); } // Smallest difference where a float is basically that value EPSILON :: 0.001; float_eq :: (f: float, with: float) -> bool { return f > with - EPSILON && f < with + EPSILON; } print_mat4 :: (m: Mat4) { basic.print("| % % % % |\n", m._00, m._01, m._02, m._03); basic.print("| % % % % |\n", m._10, m._11, m._12, m._13); basic.print("| % % % % |\n", m._20, m._21, m._22, m._23); basic.print("| % % % % |\n", m._30, m._31, m._32, m._33); }