Mat2 :: #type,distinct Vec(2 * 2, 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 { return .{ components = .[ r0.x, r0.y, r0.z, r0.w, r1.x, r1.y, r1.z, r1.w, r2.x, r2.y, r2.z, r2.w, r3.x, r3.y, r3.z, r3.w, ], }; } 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 { res: Mat4; res._00 = diag; res._11 = diag; res._22 = diag; res._33 = diag; 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 { meta.check_bounds(idx * 4, m.N); return (m.components[idx * 4]).(*Vec4).*; } operator *[] :: inline (m: *Mat4, $$idx: int) -> *Vec4 #no_abc { N :: Mat4.{}.N; // @note(judah): dumb workaround for not being able to access pointer type field constants meta.check_bounds(idx * 4, N); return (*m.components[idx * 4]).(*Vec4); } operator []= :: inline (m: *Mat4, $$idx: int, value: Vec4) #no_abc { N :: Mat4.{}.N; meta.check_bounds(idx * 4, N); ptr := (*m.components[idx * 4]).(*Vec4); 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); } #scope_file; #if #exists(RUN_TESTS) #run { test :: #import "jc/meta/test"; test.run(basic.tprint("%: Mat2", UNITS), t => { { identity := m2_identity; a: Mat2 = Mat2.{.[1, 2, 3, 4]}; test.expect(t, a*identity == a); } { rotator := rotation_mat2(from_rad(PI)); v2 := v2f(1.0, 0.5); v2 = rotator*v2; test.expect(t, v2_eq(v2, v2f(-1.0, -0.5))); v2 = rotator*v2; test.expect(t, v2_eq(v2, v2f(1.0, 0.5))); } { m := m2(1.0, 3.0, 2.0, 2.0); det := determinate(m); test.expect(t, det == -4); } { rotator := rotation_mat2(from_rad(PI)); inv_rotator := inverse(rotator); v2 := v2f(0.1, 1.0); test.expect(t, inv_rotator*rotator == m2_identity); test.expect(t, inv_rotator*(rotator*v2) == v2); v2 = rotator*v2; v2 = inv_rotator*v2; } }); test.run(basic.tprint("%: Mat4", UNITS), 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); } { 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)); } { rotator := rotation_mat4(v3f(0.0, 1.0, 0.0), from_rad(PI)); v4 := v4f(1.0, 0.5, 0.1, 1.0); v4 = rotator*v4; test.expect(t, v4_eq(v4, v4f(-1.0, 0.5, -0.1, 1.0))); v4 = rotator*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; det := determinate(camera_rotator); } { 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); test.expect(t, inv_rotator*rotator == m4_identity); v4 = rotator*v4; v4 = inv_rotator*v4; } }); }