More Vector and Matrix math and their tests

added common.jai for common math procedures
Some common procedures for smaller fixed vector sizes were made more optimal. SIMD coming later
Added tests to the math/module.jai
This commit is contained in:
jesse 2025-05-31 14:46:39 -07:00
parent 62f5d8394b
commit 526e1c8e8e
5 changed files with 1131 additions and 53 deletions

4
TODO
View file

@ -4,10 +4,8 @@
017 [x] re-implement dynamic arrays (should mirror procedures on 'Static_Array') 017 [x] re-implement dynamic arrays (should mirror procedures on 'Static_Array')
[Jesse] [Jesse]
011 [math] add more Vec math procedures
034 [math] Rect '#type,distinct Vec(4, T)' type + rectcut 034 [math] Rect '#type,distinct Vec(4, T)' type + rectcut
002 [math] use simd for Mat4 operations 002 [math] use simd for Mat4 operations
041 [math] small test suite
*** UP NEXT (NO PARTICULAR ORDER) *** *** UP NEXT (NO PARTICULAR ORDER) ***
@ -74,6 +72,8 @@
*** DONE *** *** DONE ***
041 [math] small test suite
011 [math] add more Vec math procedures
010 [x] make base jai files standalone (ie. [array], [memory], etc... big maybe) 010 [x] make base jai files standalone (ie. [array], [memory], etc... big maybe)
042 [x] use absolute imports internally (ie. #import "jc/math"), update symlink/install process in README 042 [x] use absolute imports internally (ie. #import "jc/math"), update symlink/install process in README
000 [math] add easing procedures 000 [math] add easing procedures

86
math/common.jai Normal file
View file

@ -0,0 +1,86 @@
PI :: 3.1415926;
TAU :: 6.2831853;
#if UNITS == .turns {
to_rad :: turn_to_rad;
from_rad :: rad_to_turn;
}
else #if UNITS == .radians {
to_rad :: rad_to_rad;
from_rad :: rad_to_rad;
}
else #if UNITS == .degrees {
to_rad :: deg_to_rad;
from_rad :: rad_to_deg;
}
turn_to_rad :: (turns: float64) -> float #expand #no_debug {
CONSTANT :float64: PI*2.0;
return cast(float)(CONSTANT*turns);
}
turn_to_rad :: (turns: float) -> float #expand #no_debug {
CONSTANT :float: xx PI*2.0;
return CONSTANT*turns;
}
deg_to_rad :: (deg: float64) -> float #expand #no_debug {
CONSTANT :float64: PI/180.0;
return cast(float)(CONSTANT*deg);
}
deg_to_rad :: (deg: float) -> float #expand #no_debug {
CONSTANT :float: xx (PI/180.0);
return CONSTANT*deg;
}
rad_to_rad :: (rad: float64) -> float #expand #no_debug {
return cast(float)rad;
}
rad_to_rad :: (rad: float) -> float #expand #no_debug {
return rad;
}
rad_to_turn :: (rad: float64) -> float #expand {
CONSTANT :float64: (1.0/(PI*2.0));
return cast(float)(CONSTANT*rad);
}
rad_to_turn :: (rad: float) -> float #expand {
CONSTANT :float: xx (1.0/(PI*2.0));
return CONSTANT*rad;
}
rad_to_deg :: (rad: float64) -> float #expand {
CONSTANT :float64: (180.0/PI);
return cast(float)(CONSTANT*rad);
}
rad_to_deg :: (rad: float) -> float #expand {
CONSTANT :float: xx (180.0/PI);
return CONSTANT*rad;
}
sin :: (ang: float) -> float #expand {
return math.sin(to_rad(ang));
}
cos :: (ang: float) -> float #expand {
return math.cos(to_rad(ang));
}
atan2 :: (y: float, x: float) -> float #expand {
return from_rad(math.atan2(y, x));
}
asin :: (ang: float) -> float #expand {
return from_rad(math.asin(ang));
}
abs :: (v: $T) -> T
#modify { return meta.type_is_scalar(T), "Only accepting scalar types, integer and float"; } {
return ifx v < 0 then -v else v;
}
#scope_file
// sin, cos
meta :: #import "jc/meta";
math :: #import "Math";

View file

@ -1,5 +1,24 @@
Mat2 :: #type,distinct Vec(2 * 2, float);
Mat4 :: #type,distinct Vec(4 * 4, float); Mat4 :: #type,distinct Vec(4 * 4, float);
m2 :: (_xx: float, xy: float, yx: float, yy: float) -> Mat2 #expand {
m: Mat2 = ---;
m._00 = _xx;
m._01 = xy;
m._10 = yx;
m._11 = yy;
return m;
}
m2 :: (r0: Vec2, r1: Vec2) -> Mat2 #expand {
return .{
components = .[
r0.x, r0.y,
r1.x, r1.y,
]
};
}
m4 :: (r0: Vec4, r1: Vec4, r2: Vec4, r3: Vec4) -> Mat4 #expand { m4 :: (r0: Vec4, r1: Vec4, r2: Vec4, r3: Vec4) -> Mat4 #expand {
return .{ return .{
components = .[ components = .[
@ -11,17 +30,66 @@ m4 :: (r0: Vec4, r1: Vec4, r2: Vec4, r3: Vec4) -> Mat4 #expand {
}; };
} }
m4_identity :: #bake_arguments m4d(diag = 1); m2_identity :: Mat2.{_00=1, _11=1};
m4_identity :: Mat4.{_00=1, _11=1, _22=1, _33=1};
m2d :: (diag: float) -> Mat2 #expand {
r: Mat2;
r._00 = diag;
r._11 = diag;
return r;
}
m4d :: (diag: float) -> Mat4 #expand { m4d :: (diag: float) -> Mat4 #expand {
res: Mat4; res: Mat4;
res[0][0] = diag; res._00 = diag;
res[1][1] = diag; res._11 = diag;
res[2][2] = diag; res._22 = diag;
res[3][3] = diag; res._33 = diag;
return res; return res;
} }
operator [] :: inline (m: Mat2, idx: int) -> Vec2 #expand {
bounds_check_index(idx * 2, m.N);
return (m.components[idx * 2]).(*Vec4).*;
}
operator *[] :: inline (m: *Mat2, $$idx: int) -> *Vec2 #no_abc {
N :: Mat2.{}.N; // @note(judah): dumb workaround for not being able to access pointer type field constants
bounds_check_index(idx * 2, N);
return (*m.components[idx * 2]).(*Vec4);
}
operator []= :: inline (m: *Mat2, $$idx: int, value: Vec2) #no_abc {
N :: Mat2.{}.N;
bounds_check_index(idx * 4, N);
ptr := (*m.components[idx * 4]).(*Vec2);
ptr.*= value;
}
operator* :: inline (a: Mat2, b: Mat2) -> Mat2 {
r: Mat2 = ---;
r._00 = a._00*b._00 + a._01*b._10;
r._01 = a._00*b._01 + a._01*b._11;
r._10 = a._10*b._00 + a._11*b._10;
r._11 = a._10*b._01 + a._11*b._11;
return r;
}
operator* :: inline (a: Mat2, b: Vec2) -> Vec2 {
v: Vec2 = ---;
v.x = a._00*b.x + a._01*b.y;
v.y = a._10*b.x + a._11*b.y;
return v;
}
// Note(Jesse): Probably never used except for testing
operator== :: (a: Mat2, b: Mat2) -> bool #no_abc {
for a.components if it != b.components[it_index] return false;
return true;
}
operator [] :: inline (m: Mat4, idx: int) -> Vec4 #expand { operator [] :: inline (m: Mat4, idx: int) -> Vec4 #expand {
meta.check_bounds(idx * 4, m.N); meta.check_bounds(idx * 4, m.N);
return (m.components[idx * 4]).(*Vec4).*; return (m.components[idx * 4]).(*Vec4).*;
@ -41,3 +109,294 @@ operator []= :: inline (m: *Mat4, $$idx: int, value: Vec4) #no_abc {
ptr.*= value; ptr.*= value;
} }
// Note(Jesse): Probably never used except for testing
operator== :: (a: Mat4, b: Mat4) -> bool #no_abc {
for a.components if it != b.components[it_index] return false;
return true;
}
translate :: (from: Mat4, with: Vec3) -> Mat4 {
r := from;
r._03 += with.x*r._00 + with.y*r._01 + with.z*r._02;
r._13 += with.x*r._10 + with.y*r._11 + with.z*r._12;
r._23 += with.x*r._20 + with.y*r._21 + with.z*r._22;
return r;
}
translate :: (with: Vec3) -> Mat4 {
r: Mat4 = m4_identity;
r._03 = with.x;
r._13 = with.y;
r._23 = with.z;
return r;
}
make_look_at :: (camera: Vec3, at: Vec3, up_vector: Vec3) -> Mat4
{
// -Z forward, Y up, X right
forward := normalize(at - camera);
right := normalize(cross(up_vector, forward)); // up_vector
up := normalize(cross(forward, right));
look_at: Mat4;
look_at._00 = right.x;
look_at._01 = right.y;
look_at._02 = right.z;
look_at._10 = up.x;
look_at._11 = up.y;
look_at._12 = up.z;
look_at._20 = -forward.x;
look_at._21 = -forward.y;
look_at._22 = -forward.z;
look_at._33 = 1;
look_at = inline translate(look_at, -camera);
return look_at;
}
operator* :: (a: Mat4, b: Mat4) -> Mat4 {
r: Mat4 = ---;
r._00 = a._00*b._00 + a._01*b._10 + a._02*b._20 + a._03*b._30;
r._01 = a._00*b._01 + a._01*b._11 + a._02*b._21 + a._03*b._31;
r._02 = a._00*b._02 + a._01*b._12 + a._02*b._22 + a._03*b._32;
r._03 = a._00*b._03 + a._01*b._13 + a._02*b._23 + a._03*b._33;
r._10 = a._10*b._00 + a._11*b._10 + a._12*b._20 + a._13*b._30;
r._11 = a._10*b._01 + a._11*b._11 + a._12*b._21 + a._13*b._31;
r._12 = a._10*b._02 + a._11*b._12 + a._12*b._22 + a._13*b._32;
r._13 = a._10*b._03 + a._11*b._13 + a._12*b._23 + a._13*b._33;
r._20 = a._20*b._00 + a._21*b._10 + a._22*b._20 + a._23*b._30;
r._21 = a._20*b._01 + a._21*b._11 + a._22*b._21 + a._23*b._31;
r._22 = a._20*b._02 + a._21*b._12 + a._22*b._22 + a._23*b._32;
r._23 = a._20*b._03 + a._21*b._13 + a._22*b._23 + a._23*b._33;
r._30 = a._30*b._00 + a._31*b._10 + a._32*b._20 + a._33*b._30;
r._31 = a._30*b._01 + a._31*b._11 + a._32*b._21 + a._33*b._31;
r._32 = a._30*b._02 + a._31*b._12 + a._32*b._22 + a._33*b._32;
r._33 = a._30*b._03 + a._31*b._13 + a._32*b._23 + a._33*b._33;
return r;
}
// Note(Jesse): If you want to make it symmetric go ahead, I usually don't
operator* :: (a: Mat4, v: Vec4) -> Vec4 {
r: Vec4 = ---;
r.x = a._00*v.x + a._01*v.y + a._02*v.z + a._03*v.w;
r.y = a._10*v.x + a._11*v.y + a._12*v.z + a._13*v.w;
r.z = a._20*v.x + a._21*v.y + a._22*v.z + a._23*v.w;
r.w = a._30*v.x + a._31*v.y + a._32*v.z + a._33*v.w;
return r;
}
transpose :: (m: Mat2) -> Mat2 {
r: Mat2 = ---;
// Todo(Jesse): This can be one call to simd shuffle
r._00 = m._00;
r._01 = m._10;
r._10 = m._01;
r._11 = m._11;
return r;
}
transpose :: (m: Mat4) -> Mat4 {
r: Mat4 = ---;
r._00 = m._00;
r._01 = m._10;
r._02 = m._20;
r._03 = m._30;
r._10 = m._01;
r._11 = m._11;
r._12 = m._21;
r._13 = m._31;
r._20 = m._02;
r._21 = m._12;
r._22 = m._22;
r._23 = m._32;
r._30 = m._03;
r._31 = m._13;
r._32 = m._23;
r._33 = m._33;
return r;
}
// aspect is width/height
// NDC Depth depth_01
// DirectX: 0-1 true
// Vulkan: 0-1 true
// Opengl: -1-1 false
perspective_projection :: (aspect: float, fov: float, near: float, far: float, $$depth_01: bool) -> Mat4 {
r: Mat4 = ---;
// Y up, X right, Z- forward
fov_part := 1.0/tan(fov*0.5);
denom := (far - near);
r._00 = fov_part/aspect;
r._11 = fov_part;
r._22 = -(far + near)/denom;
r._23 = -2*far*near/denom;
r._32 = -1;
r._33 = 1;
if depth_01 {
// Makes depth range from 0 to 1
// To map -1,1 depth range to 0,1 we transform z as follows: z' = z * 0.5 + 0.5
r._22 = r._22 * 0.5 + r._32 * 0.5;
r._23 = r._23 * 0.5 + r._33 * 0.5;
}
return r;
}
perspective_projection_left_handed :: (aspect: float, fov: float, near: float, far: float, $$depth_01: bool) -> Mat4 {
r: Mat4 = ---;
// Y up, X right, Z- forward
fov_part := 1.0/tan(fov*0.5);
denom := (far - near);
r._00 = fov_part/aspect;
r._11 = fov_part;
r._22 = (far + near)/denom;
r._23 = 2*far*near/denom;
r._32 = -1;
r._33 = 1;
if depth_01 {
// Makes depth range from 0 to 1
// To map -1,1 depth range to 0,1 we transform z as follows: z' = z * 0.5 + 0.5
r._22 = r._22 * 0.5 + r._32 * 0.5;
r._23 = r._23 * 0.5 + r._33 * 0.5;
}
return r;
}
// Note(Jesse): Typically left/right/top/bottom are pixel coords
// NDC Depth depth_01
// DirectX: 0-1 true
// Vulkan: 0-1 true
// Opengl: -1-1 false
orthographic_projection :: (left: float, right: float, top: float, bottom: float, near: float, far: float, $$depth_01: bool) -> Mat4 {
r: Mat4 = m4_identity;
r._00 = 2.0/(right - left);
r._11 = 2.0/(top - bottom);
r._22 = -1.0/(far - near);
if depth_01 == false then r._22 *= 2;
r._30 = -(right + left)/(right - left);
r._31 = -(top + bottom)/(top - bottom);
r._32 = -(far + near)/(far - near);
r._33 = 1;
return r;
}
rotation_mat2 :: (angle: float) -> Mat2 {
m: Mat2 = ---;
m._00 = cos(angle);
m._01 = -sin(angle);
m._10 = sin(angle);
m._11 = cos(angle);
return m;
}
rotation_mat4 :: (axis: Vec3, angle: float) -> Mat4 {
m := m4_identity;
a := normalize(axis);
sin_theta := sin(angle);
cos_theta := cos(angle);
inv_cos := 1.0 - cos_theta;
// Contributors to vector's X
m._00 = (a.x*a.x*inv_cos) + cos_theta;
m._01 = (a.x*a.y*inv_cos) + (a.z*sin_theta);
m._02 = (a.x*a.z*inv_cos) - (a.y*sin_theta);
// Contributors to vector's Y
m._10 = (a.y*a.x*inv_cos) - (a.z*sin_theta);
m._11 = (a.y*a.y*inv_cos) + cos_theta;
m._12 = (a.y*a.z*inv_cos) + (a.x*sin_theta);
// Contributors to vector's Z
m._20 = (a.z*a.x*inv_cos) + (a.y*sin_theta);
m._21 = (a.z*a.y*inv_cos) - (a.x*sin_theta);
m._22 = (a.z*a.z*inv_cos) + cos_theta;
return m;
}
rotation_mat4 :: (q: Quat) -> Mat4 {
m := m4_identity;
xs := 2*q.x;
ys := 2*q.y;
zs := 2*q.z;
wx := q.w*xs;
wy := q.w*ys;
wz := q.w*zs;
_xx := q.x*xs;
xy := q.x*ys;
xz := q.x*zs;
yy := q.y*ys;
yz := q.y*zs;
zz := q.z*zs;
m._00 = 1.0 - (yy + zz);
m._01 = xy - wz;
m._02 = xz + wy;
m._10 = xy + wz;
m._11 = 1.0 - (_xx + zz);
m._12 = yz - wx;
m._20 = xz - wy;
m._21 = yz + wx;
m._22 = 1.0 - (_xx + yy);
return m;
}
determinate :: (m: Mat2) -> float {
return m._00*m._11 - m._01*m._10;
}
determinate :: (m: Mat4) -> float {
c01 := cross(m.v4[0].xyz, m.v4[1].xyz);
c23 := cross(m.v4[2].xyz, m.v4[3].xyz);
b10 := m.v4[0].xyz*m.v4[1].w - m.v4[1].xyz*m.v4[0].w;
b32 := m.v4[2].xyz*m.v4[3].w - m.v4[3].xyz*m.v4[2].w;
return dot(c01, b32) + dot(c23, b10);
}
inverse :: (m: Mat2) -> Mat2 {
r: Mat2 = ---;
invdet := 1.0/determinate(m);
r._00 = invdet* m._11;
r._01 = invdet*-m._01;
r._10 = invdet*-m._10;
r._11 = invdet* m._00;
return r;
}
inverse :: (m: Mat4) -> Mat4 {
c01 := cross(m.v4[0].xyz, m.v4[1].xyz);
c23 := cross(m.v4[2].xyz, m.v4[3].xyz);
b10 := m.v4[0].xyz*m.v4[1].w - m.v4[1].xyz*m.v4[0].w;
b32 := m.v4[2].xyz*m.v4[3].w - m.v4[3].xyz*m.v4[2].w;
det := dot(c01, b32) + dot(c23, b10);
if abs(det) < 0.0001
return m4_identity;
inv_det := 1.0/det;
c01 *= inv_det;
c23 *= inv_det;
b10 *= inv_det;
b32 *= inv_det;
r: Mat4 = ---;
r.v4[0] = v4f(cross(m.v4[1].xyz, b32) + c23*m.v4[1].w, -dot(m.v4[1].xyz, c23));
r.v4[1] = v4f(cross(b32, m.v4[0].xyz) + c23*m.v4[0].w, dot(m.v4[0].xyz, c23));
r.v4[2] = v4f(cross(m.v4[3].xyz, b10) + c01*m.v4[3].w, -dot(m.v4[3].xyz, c01));
r.v4[3] = v4f(cross(b10, m.v4[2].xyz) + c01*m.v4[2].w, dot(m.v4[2].xyz, c01));
return transpose(r);
}

View file

@ -21,14 +21,359 @@ F64_Min, F64_Max :: #run meta.lo_for(float64), #run meta.hi_for(float64);
#load "vec.jai"; #load "vec.jai";
#load "mat.jai"; #load "mat.jai";
#load "ease.jai"; #load "ease.jai";
#load "common.jai";
#scope_module; #scope_module;
#if RUN_TESTS { #if RUN_TESTS #run {
test :: #import "jc/test"; test :: #import "jc/test";
vec2_tests();
vec3_tests();
vec4_tests();
vecn_tests();
mat2_tests();
mat4_tests();
quat_tests();
} }
#scope_file; #scope_file;
meta :: #import "jc/meta"; meta :: #import "jc/meta";
basic :: #import "Basic"; // @future 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);
}

View file

@ -66,6 +66,38 @@ Vec :: struct(N: int, T: Type)
} }
} }
// compound accessors
basic.append(*b, "#place components;\n");
if N >= 4 {
basic.append(*b, "union {\n");
basic.append(*b, "\txy: Vec2 = ---;\n");
basic.append(*b, "\txyz: Vec3 = ---;\n");
basic.append(*b, "};");
}
else if N >= 3 {
basic.append(*b, "xy: T = ---;\n");
}
// Vec4 row accessors (Possibly becoming/used as SIMD lanes)
if N/4 > 1 {
basic.append(*b, "#place components;\n");
basic.print_to_builder(*b, "v4: [%]Vec4 = ---;\n", N/4);
}
// Matrix Accessors
if N == 4 { // Mat2
basic.append(*b, "#place components;\n");
for i: 0..3 {
basic.print_to_builder(*b, "_%0%: T = ---;\n", i/2, i%2);
}
}
if N == 16 { // Mat4
basic.append(*b, "#place components;\n");
for i: 0..15 {
basic.print_to_builder(*b, "_%0%: T = ---;\n", i/4, i%4);
}
}
return basic.builder_to_string(*b); return basic.builder_to_string(*b);
}; };
} }
@ -100,118 +132,268 @@ for_expansion :: (v: *Vec, body: Code, flags: For_Flags) #expand {
operator + :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { operator + :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4 #if l.N <= 4 {
for l res[it_index] = it + r[it_index]; res.x = l.x + r.x;
res.y = l.y + r.y;
#if l.N >= 3 then res.z = l.z + r.z;
#if l.N == 4 then res.w = l.w + r.w; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it + r[it_index];
}
return res; return res;
} }
operator - :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { operator - :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4 #if l.N <= 4 {
for l res[it_index] = it - r[it_index]; res.x = l.x - r.x;
res.y = l.y - r.y;
#if l.N >= 3 then res.z = l.z - r.z;
#if l.N == 4 then res.w = l.w - r.w; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it - r[it_index];
}
return res; return res;
} }
operator * :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { operator * :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4 #if l.N <= 4 {
for l res[it_index] = it * r[it_index]; res.x = l.x*r.x;
res.y = l.y*r.y;
#if l.N >= 3 then res.z = l.z*r.z;
#if l.N == 4 then res.w = l.w*r.w; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it * r[it_index];
}
return res; return res;
} }
operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
// @todo(judah): unroll for N <= 4 #if l.N <= 4 {
for l res[it_index] = it / r[it_index]; res.x = l.x/r.x;
res.y = l.y/r.y;
#if l.N >= 3 then res.z = l.z/r.z;
#if l.N == 4 then res.w = l.w/r.w; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it / r[it_index];
}
return res; return res;
} }
operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric operator + :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
for l res[it_index] = it + r; #if l.N <= 4 {
res.x = l.x + r;
res.y = l.y + r;
#if l.N >= 3 then res.z = l.z + r;
#if l.N == 4 then res.w = l.w + r; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it + r;
}
return res; return res;
} }
operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc operator - :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
for l res[it_index] = it - r; #if l.N <= 4 {
res.x = l.x - r;
res.y = l.y - r;
#if l.N >= 3 then res.z = l.z - r;
#if l.N == 4 then res.w = l.w - r; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it - r;
}
return res; return res;
} }
operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc operator - :: inline (l: $R, r: Vec) -> Vec(l.N, l.T) #no_abc
#modify { return meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
for l res[it_index] = r - it; #if l.N <= 4 {
res.x = l - r.x;
res.y = l - r.y;
#if r.N >= 3 then res.z = l - r.z;
#if r.N == 4 then res.w = l - r.w; // @todo(jesse): SIMD
}
else {
for l res[it_index] = r - it;
}
return res;
}
operator- :: inline(v: Vec) -> Vec(v.N, v.T) #no_abc {
res: Vec(v.N, v.T) = ---;
#if v.N <= 4 {
res.x = -v.x;
res.y = -v.y;
#if v.N >= 3 then res.z = -v.z;
#if v.N == 4 then res.w = -v.w; // @todo(jesse): SIMD
}
else {
for v res[it_index] = -it;
}
return res; return res;
} }
operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric operator * :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc #symmetric
#modify { return meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
for l res[it_index] = it*r; #if l.N <= 4 {
res.x = l.x*r;
res.y = l.y*r;
#if l.N >= 3 then res.z = l.z*r;
#if l.N == 4 then res.w = l.w*r; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it*r;
}
return res; return res;
} }
operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc operator / :: inline (l: Vec, r: $R) -> Vec(l.N, l.T) #no_abc
#modify { return meta.type_is_scalar(R), "type is not integer or float"; } { #modify { return meta.type_is_scalar(R), "type is not integer or float"; } {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
for l res[it_index] = it/r; #if l.N <= 4 {
res.x = l.x/r;
res.y = l.y/r;
#if l.N >= 3 then res.z = l.z/r;
#if l.N == 4 then res.w = l.w/r; // @todo(jesse): SIMD
}
else {
for l res[it_index] = it/r;
}
return res; return res;
} }
operator == :: inline (l: Vec, r: Vec(l.N, l.T)) -> bool #no_abc { operator == :: inline (l: Vec, r: Vec(l.N, l.T)) -> bool #no_abc {
for l if it != r[it_index] return false; #if l.N <= 4 {
return true; res: bool = l.x == r.x && l.y == r.y;
#if l.N >= 3 then res &= l.z == r.z;
#if l.N == 4 then res &= l.w == r.w; // @todo(jesse): SIMD
return res;
}
else {
for l if it != r[it_index] return false;
return true;
}
} }
min :: (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { min :: (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
n := l.N - 1; #if l.N <= 4 {
while n >= 0 { res.x = ifx l.x < r.x then l.x else r.x;
if l[n] < r[n] res.y = ifx l.y < r.y then l.y else r.y;
res[n] = l[n]; #if l.N >= 3 then res.z = ifx l.z < r.z then l.z else r.z;
else #if l.N == 4 then res.w = ifx l.w < r.w then l.w else r.w; // @todo(jesse): SIMD
res[n] = r[n]; }
n -= 1; else {
n := l.N - 1;
while n >= 0 {
if l[n] < r[n]
res[n] = l[n];
else
res[n] = r[n];
n -= 1;
}
} }
return res; return res;
} }
max :: (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { max :: (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---; res: Vec(l.N, l.T) = ---;
n := l.N - 1; #if l.N <= 4 {
while n >= 0 { res.x = ifx l.x > r.x then l.x else r.x;
if l[n] > r[n] res.y = ifx l.y > r.y then l.y else r.y;
res[n] = l[n]; #if l.N >= 3 then res.z = ifx l.z > r.z then l.z else r.z;
else #if l.N == 4 then res.w = ifx l.w > r.w then l.w else r.w; // @todo(jesse): SIMD
res[n] = r[n]; }
n -= 1; else {
n := l.N - 1;
while n >= 0 {
if l[n] > r[n]
res[n] = l[n];
else
res[n] = r[n];
n -= 1;
}
} }
return res; return res;
} }
ceil :: (l: Vec, x: l.T) -> Vec(l.N, l.T) #no_abc { min :: (l: Vec, r: l.T) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
#if l.N <= 4 {
res.x = ifx l.x > r then l.x else r;
res.y = ifx l.y > r then l.y else r;
#if l.N >= 3 then res.z = ifx l.z > r.z then l.z else r;
#if l.N == 4 then res.w = ifx l.w > r.w then l.w else r; // @todo(jesse): SIMD
}
else {
n := l.N - 1;
while n >= 0 {
if l[n] > r
res[n] = l[n];
else
res[n] = r;
n -= 1;
}
}
return res;
}
max :: (l: Vec, r: l.T) -> Vec(l.N, l.T) #no_abc {
res: Vec(l.N, l.T) = ---;
#if l.N <= 4 {
res.x = ifx l.x < r then l.x else r;
res.y = ifx l.y < r then l.y else r;
#if l.N >= 3 then res.z = ifx l.z < r then l.z else r;
#if l.N == 4 then res.w = ifx l.w < r then l.w else r; // @todo(jesse): SIMD
}
else {
n := l.N - 1;
while n >= 0 {
if l[n] < r
res[n] = l[n];
else
res[n] = r;
n -= 1;
}
}
return res;
}
// @todo(jesse): SIMD
ceil :: (l: Vec) -> Vec(l.N, l.T) #no_abc {
r: Vec(l.N, l.T) = ---; r: Vec(l.N, l.T) = ---;
n := l.N - 1; n := l.N - 1;
sign: float;
while n >= 0 { while n >= 0 {
if x < l[n] int_part := l[n].(int);
r[n] = x; add_part := ifx l[n] > int_part.(float) then 1.0 else 0.0;
else r[n] = int_part + add_part;
r[n] = l[n];
n -= 1; n -= 1;
} }
return r; return r;
} }
floor :: (l: Vec, x: l.T) -> Vec(l.N, l.T) #no_abc { // @todo(jesse): SIMD
floor :: (l: Vec) -> Vec(l.N, l.T) #no_abc {
r: Vec(l.N, l.T) = ---; r: Vec(l.N, l.T) = ---;
n := l.N - 1; n := l.N - 1;
sign: float;
while n >= 0 { while n >= 0 {
if x > l[n] int_part := l[n].(int);
r[n] = x; sub_part := ifx l[n] < int_part.(float) then 1.0 else 0.0;
else r[n] = int_part - sub_part;
r[n] = l[n];
n -= 1; n -= 1;
} }
return r; return r;
@ -243,11 +425,11 @@ dot :: (a: Vec, b: Vec(a.N, a.T)) -> a.T #no_abc {
} }
length_squared :: (v: Vec) -> float #no_abc { length_squared :: (v: Vec) -> float #no_abc {
return dot(v, v); return inline dot(v, v);
} }
length :: (v: Vec) -> float #no_abc { length :: (v: Vec) -> float #no_abc {
return math.sqrt(dot(v, v)); return math.sqrt(inline dot(v, v));
} }
abs :: (v: Vec) -> Vec(v.N, v.T) #no_abc { abs :: (v: Vec) -> Vec(v.N, v.T) #no_abc {
@ -263,9 +445,11 @@ abs :: (v: Vec) -> Vec(v.N, v.T) #no_abc {
return r; return r;
} }
// @todo(Jesse): SIMD
norm :: normalize; norm :: normalize;
normalize :: (v: Vec) -> Vec(v.N, v.T) #no_abc { normalize :: (v: Vec) -> Vec(v.N, v.T) #no_abc {
return v/length(v); inv_len := 1.0/length(v);
return inv_len*v;
} }
lerp :: (a: Vec, b: Vec(a.N, a.T), t: float) -> Vec(a.N, a.T) #no_abc { lerp :: (a: Vec, b: Vec(a.N, a.T), t: float) -> Vec(a.N, a.T) #no_abc {
@ -307,7 +491,7 @@ round :: (v: Vec($N, $T)) -> Vec(N, T) #no_abc
Vec2 :: Vec(2, float); Vec2 :: Vec(2, float);
Vec3 :: Vec(3, float); Vec3 :: Vec(3, float);
Vec4 :: Vec(4, float); Vec4 :: Vec(4, float);
Quat :: Vec4; Quat :: #type,distinct Vec4; // Note(Jesse): I Had to make this distinct, otherwise operators stomp on eachother
v2f :: (x: $T = 0, y: T = 0) -> Vec2 v2f :: (x: $T = 0, y: T = 0) -> Vec2
#modify { return meta.type_is_float(T), "use v2i for integer arguments"; } #modify { return meta.type_is_float(T), "use v2i for integer arguments"; }
@ -339,18 +523,122 @@ v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }
v4f :: (v: Vec3, $$w: float) -> Vec4 #expand {
return .{ xyz=v, w=w };
}
v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T) v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T)
#modify { return meta.type_is_integer(T), "use v4f for float arguments"; } #modify { return meta.type_is_integer(T), "use v4f for float arguments"; }
#expand { #expand {
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }
quat :: (x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> Quat #expand { quat :: (x: float = 0, y: float = 0, z: float = 0, w: float = 1) -> Quat #expand {
return .{ x = x, y = y, z = z, w = w }; return .{ x = x, y = y, z = z, w = w };
} }
quat :: (xyz: Vec3, w: float) -> Quat #expand {
return .{xyz=xyz, w=w};
}
#scope_file; quat_identity :: Quat.{x=0, y=0, z=0, w=1};
operator* :: (a: Quat, b: Quat) -> Quat {
r: Quat = ---;
r.xyz = a.w*b.xyz + b.w*a.xyz + cross(a.xyz, b.xyz);
r.w = a.w*b.w - dot(a.xyz, b.xyz);
return r;
}
operator* :: (q: Quat, v: Vec3) -> Vec3 #symmetric {
r: Vec3 = 2*cross(q.xyz, v);
return v + q.w*r + cross(q.xyz, r);
}
operator* :: (q: Quat, r: $T) -> Quat #symmetric {
return .{x=q.x*r, y=q.y*r, z=q.z*r, w=q.w*r};
}
operator+ :: (a: Quat, b: Quat) -> Quat {
return .{x=a.x*b.x, y=a.y*b.y, z=a.z*b.z, w=a.w*b.w};
}
operator/ :: (l: Quat, r: $T) -> Quat {
return .{xyz=l.xyz/r, w=l.w/r};
}
operator== :: (l: Quat, r: Quat) -> bool {
return l.x == r.x && l.y == r.y && l.z == r.z && l.w == r.w;
}
conjugate :: (q: Quat) -> Quat #expand {
r: Quat = ---;
r.xyz = -q.xyz;
r.w = q.w;
return r;
}
// Note(Jesse): iff q is a unit vector
inverse_unit :: (q: Quat) -> Quat {
return inline conjugate(q);
}
inverse :: (q: Quat) -> Quat {
return conjugate(q)/length(q);
}
rotation_quat :: (theta: float, axis: Vec3) -> Quat {
q: Quat = ---;
a := normalize(axis);
q.xyz = sin(theta/2.0)*a;
q.w = cos(theta/2.0);
return q;
}
operator- :: (q: Quat) -> Quat {
r: Quat = ---;
r.x = -q.x;
r.y = -q.y;
r.z = -q.z;
r.w = -q.w;
return r;
}
length_squared :: (q: Quat) -> float {
return dot(q, q);
}
length :: (q: Quat) -> float {
return math.sqrt(dot(q, q));
}
dot :: (a: Quat, b: Quat) -> float {
return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}
slerp :: (a: Quat, _b: Quat, t: float) -> Quat {
b := _b;
cos_a := dot(a, b);
if cos_a < 0.0 {
cos_a = -cos_a;
b = -b;
}
alpha := math.acos(cos_a);
sin_a := math.sin(alpha);
p1 := math.sin((1-t)*alpha)/sin_a;
p2 := math.sin(t*alpha)/sin_a;
return a*p1 + b*p2;
}
cross :: (a: Vec3, b: Vec3) -> Vec3 {
v: Vec3 = ---;
v.x = a.y*b.z - a.z*b.y;
v.y = a.z*b.x - a.x*b.z;
v.z = a.x*b.y - a.y*b.x;
return v;
}
#scope_file
meta :: #import "jc/meta"; meta :: #import "jc/meta";
math :: #import "Math"; // @future math :: #import "Math"; // @future