4569 lines
170 KiB
Zig
4569 lines
170 KiB
Zig
|
// ==============================================================================
|
||
|
//
|
||
|
// SIMD math library for game developers
|
||
|
// https://github.com/michal-z/zig-gamedev/tree/main/libs/zmath
|
||
|
//
|
||
|
// Should work on all OSes supported by Zig. Works on x86_64 and ARM.
|
||
|
// Provides ~140 optimized routines and ~70 extensive tests.
|
||
|
// Can be used with any graphics API.
|
||
|
//
|
||
|
// zmath uses row-major matrices, row vectors (each row vector is stored in a SIMD register).
|
||
|
// Handedness is determined by which function version is used (Rh vs. Lh),
|
||
|
// otherwise the function works with either left-handed or right-handed view coordinates.
|
||
|
//
|
||
|
// const va = f32x4(1.0, 2.0, 3.0, 1.0);
|
||
|
// const vb = f32x4(-1.0, 1.0, -1.0, 1.0);
|
||
|
// const v0 = va + vb - f32x4(0.0, 1.0, 0.0, 1.0) * f32x4s(3.0);
|
||
|
// const v1 = cross3(va, vb) + f32x4(1.0, 1.0, 1.0, 1.0);
|
||
|
// const v2 = va + dot3(va, vb) / v1; // dotN() returns scalar replicated on all vector components
|
||
|
//
|
||
|
// const m = rotationX(math.pi * 0.25);
|
||
|
// const v = f32x4(...);
|
||
|
// const v0 = mul(v, m); // 'v' treated as a row vector
|
||
|
// const v1 = mul(m, v); // 'v' treated as a column vector
|
||
|
// const f = m[row][column];
|
||
|
//
|
||
|
// const b = va < vb;
|
||
|
// if (all(b, 0)) { ... } // '0' means check all vector components; if all are 'true'
|
||
|
// if (all(b, 3)) { ... } // '3' means check first three vector components; if all first three are 'true'
|
||
|
// if (any(b, 0)) { ... } // '0' means check all vector components; if any is 'true'
|
||
|
// if (any(b, 3)) { ... } // '3' means check first three vector components; if any from first three is 'true'
|
||
|
//
|
||
|
// var v4 = load(mem[0..], F32x4, 0);
|
||
|
// var v8 = load(mem[100..], F32x8, 0);
|
||
|
// var v16 = load(mem[200..], F32x16, 0);
|
||
|
//
|
||
|
// var camera_position = [3]f32{ 1.0, 2.0, 3.0 };
|
||
|
// var cam_pos = loadArr3(camera_position);
|
||
|
// ...
|
||
|
// storeArr3(&camera_position, cam_pos);
|
||
|
//
|
||
|
// v4 = sin(v4); // SIMDx4
|
||
|
// v8 = cos(v8); // .x86_64 -> 2 x SIMDx4, .x86_64+avx+fma -> SIMDx8
|
||
|
// v16 = atan(v16); // .x86_64 -> 4 x SIMDx4, .x86_64+avx+fma -> 2 x SIMDx8, .x86_64+avx512f -> SIMDx16
|
||
|
//
|
||
|
// store(mem[0..], v4, 0);
|
||
|
// store(mem[100..], v8, 0);
|
||
|
// store(mem[200..], v16, 0);
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// 1. Initialization functions
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// f32x4(e0: f32, e1: f32, e2: f32, e3: f32) F32x4
|
||
|
// f32x8(e0: f32, e1: f32, e2: f32, e3: f32, e4: f32, e5: f32, e6: f32, e7: f32) F32x8
|
||
|
// f32x16(e0: f32, e1: f32, e2: f32, e3: f32, e4: f32, e5: f32, e6: f32, e7: f32,
|
||
|
// e8: f32, e9: f32, ea: f32, eb: f32, ec: f32, ed: f32, ee: f32, ef: f32) F32x16
|
||
|
//
|
||
|
// f32x4s(e0: f32) F32x4
|
||
|
// f32x8s(e0: f32) F32x8
|
||
|
// f32x16s(e0: f32) F32x16
|
||
|
//
|
||
|
// boolx4(e0: bool, e1: bool, e2: bool, e3: bool) Boolx4
|
||
|
// boolx8(e0: bool, e1: bool, e2: bool, e3: bool, e4: bool, e5: bool, e6: bool, e7: bool) Boolx8
|
||
|
// boolx16(e0: bool, e1: bool, e2: bool, e3: bool, e4: bool, e5: bool, e6: bool, e7: bool,
|
||
|
// e8: bool, e9: bool, ea: bool, eb: bool, ec: bool, ed: bool, ee: bool, ef: bool) Boolx16
|
||
|
//
|
||
|
// load(mem: []const f32, comptime T: type, comptime len: u32) T
|
||
|
// store(mem: []f32, v: anytype, comptime len: u32) void
|
||
|
//
|
||
|
// loadArr2(arr: [2]f32) F32x4
|
||
|
// loadArr2zw(arr: [2]f32, z: f32, w: f32) F32x4
|
||
|
// loadArr3(arr: [3]f32) F32x4
|
||
|
// loadArr3w(arr: [3]f32, w: f32) F32x4
|
||
|
// loadArr4(arr: [4]f32) F32x4
|
||
|
//
|
||
|
// storeArr2(arr: *[2]f32, v: F32x4) void
|
||
|
// storeArr3(arr: *[3]f32, v: F32x4) void
|
||
|
// storeArr4(arr: *[4]f32, v: F32x4) void
|
||
|
//
|
||
|
// arr3Ptr(ptr: anytype) *const [3]f32
|
||
|
// arrNPtr(ptr: anytype) [*]const f32
|
||
|
//
|
||
|
// splat(comptime T: type, value: f32) T
|
||
|
// splatInt(comptime T: type, value: u32) T
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// 2. Functions that work on all vector components (F32xN = F32x4 or F32x8 or F32x16)
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// all(vb: anytype, comptime len: u32) bool
|
||
|
// any(vb: anytype, comptime len: u32) bool
|
||
|
//
|
||
|
// isNearEqual(v0: F32xN, v1: F32xN, epsilon: F32xN) BoolxN
|
||
|
// isNan(v: F32xN) BoolxN
|
||
|
// isInf(v: F32xN) BoolxN
|
||
|
// isInBounds(v: F32xN, bounds: F32xN) BoolxN
|
||
|
//
|
||
|
// andInt(v0: F32xN, v1: F32xN) F32xN
|
||
|
// andNotInt(v0: F32xN, v1: F32xN) F32xN
|
||
|
// orInt(v0: F32xN, v1: F32xN) F32xN
|
||
|
// norInt(v0: F32xN, v1: F32xN) F32xN
|
||
|
// xorInt(v0: F32xN, v1: F32xN) F32xN
|
||
|
//
|
||
|
// minFast(v0: F32xN, v1: F32xN) F32xN
|
||
|
// maxFast(v0: F32xN, v1: F32xN) F32xN
|
||
|
// min(v0: F32xN, v1: F32xN) F32xN
|
||
|
// max(v0: F32xN, v1: F32xN) F32xN
|
||
|
// round(v: F32xN) F32xN
|
||
|
// floor(v: F32xN) F32xN
|
||
|
// trunc(v: F32xN) F32xN
|
||
|
// ceil(v: F32xN) F32xN
|
||
|
// clamp(v0: F32xN, v1: F32xN) F32xN
|
||
|
// clampFast(v0: F32xN, v1: F32xN) F32xN
|
||
|
// saturate(v: F32xN) F32xN
|
||
|
// saturateFast(v: F32xN) F32xN
|
||
|
// lerp(v0: F32xN, v1: F32xN, t: f32) F32xN
|
||
|
// lerpV(v0: F32xN, v1: F32xN, t: F32xN) F32xN
|
||
|
// lerpInverse(v0: F32xN, v1: F32xN, t: f32) F32xN
|
||
|
// lerpInverseV(v0: F32xN, v1: F32xN, t: F32xN) F32xN
|
||
|
// mapLinear(v: F32xN, min1: f32, max1: f32, min2: f32, max2: f32) F32xN
|
||
|
// mapLinearV(v: F32xN, min1: F32xN, max1: F32xN, min2: F32xN, max2: F32xN) F32xN
|
||
|
// sqrt(v: F32xN) F32xN
|
||
|
// abs(v: F32xN) F32xN
|
||
|
// mod(v0: F32xN, v1: F32xN) F32xN
|
||
|
// modAngle(v: F32xN) F32xN
|
||
|
// mulAdd(v0: F32xN, v1: F32xN, v2: F32xN) F32xN
|
||
|
// select(mask: BoolxN, v0: F32xN, v1: F32xN)
|
||
|
// sin(v: F32xN) F32xN
|
||
|
// cos(v: F32xN) F32xN
|
||
|
// sincos(v: F32xN) [2]F32xN
|
||
|
// asin(v: F32xN) F32xN
|
||
|
// acos(v: F32xN) F32xN
|
||
|
// atan(v: F32xN) F32xN
|
||
|
// atan2(vy: F32xN, vx: F32xN) F32xN
|
||
|
// cmulSoa(re0: F32xN, im0: F32xN, re1: F32xN, im1: F32xN) [2]F32xN
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// 3. 2D, 3D, 4D vector functions
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// swizzle(v: Vec, c, c, c, c) Vec (comptime c = .x | .y | .z | .w)
|
||
|
// dot2(v0: Vec, v1: Vec) F32x4
|
||
|
// dot3(v0: Vec, v1: Vec) F32x4
|
||
|
// dot4(v0: Vec, v1: Vec) F32x4
|
||
|
// cross3(v0: Vec, v1: Vec) Vec
|
||
|
// lengthSq2(v: Vec) F32x4
|
||
|
// lengthSq3(v: Vec) F32x4
|
||
|
// lengthSq4(v: Vec) F32x4
|
||
|
// length2(v: Vec) F32x4
|
||
|
// length3(v: Vec) F32x4
|
||
|
// length4(v: Vec) F32x4
|
||
|
// normalize2(v: Vec) Vec
|
||
|
// normalize3(v: Vec) Vec
|
||
|
// normalize4(v: Vec) Vec
|
||
|
//
|
||
|
// vecToArr2(v: Vec) [2]f32
|
||
|
// vecToArr3(v: Vec) [3]f32
|
||
|
// vecToArr4(v: Vec) [4]f32
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// 4. Matrix functions
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// identity() Mat
|
||
|
// mul(m0: Mat, m1: Mat) Mat
|
||
|
// mul(s: f32, m: Mat) Mat
|
||
|
// mul(m: Mat, s: f32) Mat
|
||
|
// mul(v: Vec, m: Mat) Vec
|
||
|
// mul(m: Mat, v: Vec) Vec
|
||
|
// transpose(m: Mat) Mat
|
||
|
// rotationX(angle: f32) Mat
|
||
|
// rotationY(angle: f32) Mat
|
||
|
// rotationZ(angle: f32) Mat
|
||
|
// translation(x: f32, y: f32, z: f32) Mat
|
||
|
// translationV(v: Vec) Mat
|
||
|
// scaling(x: f32, y: f32, z: f32) Mat
|
||
|
// scalingV(v: Vec) Mat
|
||
|
// lookToLh(eyepos: Vec, eyedir: Vec, updir: Vec) Mat
|
||
|
// lookAtLh(eyepos: Vec, focuspos: Vec, updir: Vec) Mat
|
||
|
// lookToRh(eyepos: Vec, eyedir: Vec, updir: Vec) Mat
|
||
|
// lookAtRh(eyepos: Vec, focuspos: Vec, updir: Vec) Mat
|
||
|
// perspectiveFovLh(fovy: f32, aspect: f32, near: f32, far: f32) Mat
|
||
|
// perspectiveFovRh(fovy: f32, aspect: f32, near: f32, far: f32) Mat
|
||
|
// perspectiveFovLhGl(fovy: f32, aspect: f32, near: f32, far: f32) Mat
|
||
|
// perspectiveFovRhGl(fovy: f32, aspect: f32, near: f32, far: f32) Mat
|
||
|
// orthographicLh(w: f32, h: f32, near: f32, far: f32) Mat
|
||
|
// orthographicRh(w: f32, h: f32, near: f32, far: f32) Mat
|
||
|
// orthographicLhGl(w: f32, h: f32, near: f32, far: f32) Mat
|
||
|
// orthographicRhGl(w: f32, h: f32, near: f32, far: f32) Mat
|
||
|
// orthographicOffCenterLh(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat
|
||
|
// orthographicOffCenterRh(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat
|
||
|
// orthographicOffCenterLhGl(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat
|
||
|
// orthographicOffCenterRhGl(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat
|
||
|
// determinant(m: Mat) F32x4
|
||
|
// inverse(m: Mat) Mat
|
||
|
// inverseDet(m: Mat, det: ?*F32x4) Mat
|
||
|
// matToQuat(m: Mat) Quat
|
||
|
// matFromAxisAngle(axis: Vec, angle: f32) Mat
|
||
|
// matFromNormAxisAngle(axis: Vec, angle: f32) Mat
|
||
|
// matFromQuat(quat: Quat) Mat
|
||
|
// matFromRollPitchYaw(pitch: f32, yaw: f32, roll: f32) Mat
|
||
|
// matFromRollPitchYawV(angles: Vec) Mat
|
||
|
// matFromArr(arr: [16]f32) Mat
|
||
|
//
|
||
|
// loadMat(mem: []const f32) Mat
|
||
|
// loadMat43(mem: []const f32) Mat
|
||
|
// loadMat34(mem: []const f32) Mat
|
||
|
// storeMat(mem: []f32, m: Mat) void
|
||
|
// storeMat43(mem: []f32, m: Mat) void
|
||
|
// storeMat34(mem: []f32, m: Mat) void
|
||
|
//
|
||
|
// matToArr(m: Mat) [16]f32
|
||
|
// matToArr43(m: Mat) [12]f32
|
||
|
// matToArr34(m: Mat) [12]f32
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// 5. Quaternion functions
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// qmul(q0: Quat, q1: Quat) Quat
|
||
|
// qidentity() Quat
|
||
|
// conjugate(quat: Quat) Quat
|
||
|
// inverse(q: Quat) Quat
|
||
|
// rotate(q: Quat, v: Vec) Vec
|
||
|
// slerp(q0: Quat, q1: Quat, t: f32) Quat
|
||
|
// slerpV(q0: Quat, q1: Quat, t: F32x4) Quat
|
||
|
// quatToMat(quat: Quat) Mat
|
||
|
// quatToAxisAngle(quat: Quat, axis: *Vec, angle: *f32) void
|
||
|
// quatFromMat(m: Mat) Quat
|
||
|
// quatFromAxisAngle(axis: Vec, angle: f32) Quat
|
||
|
// quatFromNormAxisAngle(axis: Vec, angle: f32) Quat
|
||
|
// quatFromRollPitchYaw(pitch: f32, yaw: f32, roll: f32) Quat
|
||
|
// quatFromRollPitchYawV(angles: Vec) Quat
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// 6. Color functions
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// adjustSaturation(color: F32x4, saturation: f32) F32x4
|
||
|
// adjustContrast(color: F32x4, contrast: f32) F32x4
|
||
|
// rgbToHsl(rgb: F32x4) F32x4
|
||
|
// hslToRgb(hsl: F32x4) F32x4
|
||
|
// rgbToHsv(rgb: F32x4) F32x4
|
||
|
// hsvToRgb(hsv: F32x4) F32x4
|
||
|
// rgbToSrgb(rgb: F32x4) F32x4
|
||
|
// srgbToRgb(srgb: F32x4) F32x4
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// X. Misc functions
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// linePointDistance(linept0: Vec, linept1: Vec, pt: Vec) F32x4
|
||
|
// sin(v: f32) f32
|
||
|
// cos(v: f32) f32
|
||
|
// sincos(v: f32) [2]f32
|
||
|
// asin(v: f32) f32
|
||
|
// acos(v: f32) f32
|
||
|
//
|
||
|
// fftInitUnityTable(unitytable: []F32x4) void
|
||
|
// fft(re: []F32x4, im: []F32x4, unitytable: []const F32x4) void
|
||
|
// ifft(re: []F32x4, im: []const F32x4, unitytable: []const F32x4) void
|
||
|
//
|
||
|
// ==============================================================================
|
||
|
|
||
|
// Fundamental types
|
||
|
pub const F32x4 = @Vector(4, f32);
|
||
|
pub const F32x8 = @Vector(8, f32);
|
||
|
pub const F32x16 = @Vector(16, f32);
|
||
|
pub const Boolx4 = @Vector(4, bool);
|
||
|
pub const Boolx8 = @Vector(8, bool);
|
||
|
pub const Boolx16 = @Vector(16, bool);
|
||
|
|
||
|
// "Higher-level" aliases
|
||
|
pub const Vec = F32x4;
|
||
|
pub const Mat = [4]F32x4;
|
||
|
pub const Quat = F32x4;
|
||
|
|
||
|
const builtin = @import("builtin");
|
||
|
const std = @import("std");
|
||
|
const math = std.math;
|
||
|
const assert = std.debug.assert;
|
||
|
const expect = std.testing.expect;
|
||
|
|
||
|
const cpu_arch = builtin.cpu.arch;
|
||
|
const has_avx = if (cpu_arch == .x86_64) std.Target.x86.featureSetHas(builtin.cpu.features, .avx) else false;
|
||
|
const has_avx512f = if (cpu_arch == .x86_64) std.Target.x86.featureSetHas(builtin.cpu.features, .avx512f) else false;
|
||
|
const has_fma = if (cpu_arch == .x86_64) std.Target.x86.featureSetHas(builtin.cpu.features, .fma) else false;
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 1. Initialization functions
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub inline fn f32x4(e0: f32, e1: f32, e2: f32, e3: f32) F32x4 {
|
||
|
return .{ e0, e1, e2, e3 };
|
||
|
}
|
||
|
pub inline fn f32x8(e0: f32, e1: f32, e2: f32, e3: f32, e4: f32, e5: f32, e6: f32, e7: f32) F32x8 {
|
||
|
return .{ e0, e1, e2, e3, e4, e5, e6, e7 };
|
||
|
}
|
||
|
// zig fmt: off
|
||
|
pub inline fn f32x16(
|
||
|
e0: f32, e1: f32, e2: f32, e3: f32, e4: f32, e5: f32, e6: f32, e7: f32,
|
||
|
e8: f32, e9: f32, ea: f32, eb: f32, ec: f32, ed: f32, ee: f32, ef: f32) F32x16 {
|
||
|
return .{ e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef };
|
||
|
}
|
||
|
// zig fmt: on
|
||
|
|
||
|
pub inline fn f32x4s(e0: f32) F32x4 {
|
||
|
return splat(F32x4, e0);
|
||
|
}
|
||
|
pub inline fn f32x8s(e0: f32) F32x8 {
|
||
|
return splat(F32x8, e0);
|
||
|
}
|
||
|
pub inline fn f32x16s(e0: f32) F32x16 {
|
||
|
return splat(F32x16, e0);
|
||
|
}
|
||
|
|
||
|
pub inline fn boolx4(e0: bool, e1: bool, e2: bool, e3: bool) Boolx4 {
|
||
|
return .{ e0, e1, e2, e3 };
|
||
|
}
|
||
|
pub inline fn boolx8(e0: bool, e1: bool, e2: bool, e3: bool, e4: bool, e5: bool, e6: bool, e7: bool) Boolx8 {
|
||
|
return .{ e0, e1, e2, e3, e4, e5, e6, e7 };
|
||
|
}
|
||
|
// zig fmt: off
|
||
|
pub inline fn boolx16(
|
||
|
e0: bool, e1: bool, e2: bool, e3: bool, e4: bool, e5: bool, e6: bool, e7: bool,
|
||
|
e8: bool, e9: bool, ea: bool, eb: bool, ec: bool, ed: bool, ee: bool, ef: bool) Boolx16 {
|
||
|
return .{ e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef };
|
||
|
}
|
||
|
// zig fmt: on
|
||
|
|
||
|
pub inline fn veclen(comptime T: type) comptime_int {
|
||
|
return @typeInfo(T).Vector.len;
|
||
|
}
|
||
|
|
||
|
pub inline fn splat(comptime T: type, value: f32) T {
|
||
|
return @splat(value);
|
||
|
}
|
||
|
pub inline fn splatInt(comptime T: type, value: u32) T {
|
||
|
return @splat(@bitCast(value));
|
||
|
}
|
||
|
|
||
|
pub fn load(mem: []const f32, comptime T: type, comptime len: u32) T {
|
||
|
var v = splat(T, 0.0);
|
||
|
const loop_len = if (len == 0) veclen(T) else len;
|
||
|
comptime var i: u32 = 0;
|
||
|
inline while (i < loop_len) : (i += 1) {
|
||
|
v[i] = mem[i];
|
||
|
}
|
||
|
return v;
|
||
|
}
|
||
|
test "zmath.load" {
|
||
|
const a = [7]f32{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 };
|
||
|
var ptr = &a;
|
||
|
var i: u32 = 0;
|
||
|
const v0 = load(a[i..], F32x4, 2);
|
||
|
try expectVecEqual(v0, F32x4{ 1.0, 2.0, 0.0, 0.0 });
|
||
|
i += 2;
|
||
|
const v1 = load(a[i .. i + 2], F32x4, 2);
|
||
|
try expectVecEqual(v1, F32x4{ 3.0, 4.0, 0.0, 0.0 });
|
||
|
const v2 = load(a[5..7], F32x4, 2);
|
||
|
try expectVecEqual(v2, F32x4{ 6.0, 7.0, 0.0, 0.0 });
|
||
|
const v3 = load(ptr[1..], F32x4, 2);
|
||
|
try expectVecEqual(v3, F32x4{ 2.0, 3.0, 0.0, 0.0 });
|
||
|
i += 1;
|
||
|
const v4 = load(ptr[i .. i + 2], F32x4, 2);
|
||
|
try expectVecEqual(v4, F32x4{ 4.0, 5.0, 0.0, 0.0 });
|
||
|
}
|
||
|
|
||
|
pub fn store(mem: []f32, v: anytype, comptime len: u32) void {
|
||
|
const T = @TypeOf(v);
|
||
|
const loop_len = if (len == 0) veclen(T) else len;
|
||
|
comptime var i: u32 = 0;
|
||
|
inline while (i < loop_len) : (i += 1) {
|
||
|
mem[i] = v[i];
|
||
|
}
|
||
|
}
|
||
|
test "zmath.store" {
|
||
|
var a = [7]f32{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 };
|
||
|
const v = load(a[1..], F32x4, 3);
|
||
|
store(a[2..], v, 4);
|
||
|
try expect(a[0] == 1.0);
|
||
|
try expect(a[1] == 2.0);
|
||
|
try expect(a[2] == 2.0);
|
||
|
try expect(a[3] == 3.0);
|
||
|
try expect(a[4] == 4.0);
|
||
|
try expect(a[5] == 0.0);
|
||
|
}
|
||
|
|
||
|
pub inline fn loadArr2(arr: [2]f32) F32x4 {
|
||
|
return f32x4(arr[0], arr[1], 0.0, 0.0);
|
||
|
}
|
||
|
pub inline fn loadArr2zw(arr: [2]f32, z: f32, w: f32) F32x4 {
|
||
|
return f32x4(arr[0], arr[1], z, w);
|
||
|
}
|
||
|
pub inline fn loadArr3(arr: [3]f32) F32x4 {
|
||
|
return f32x4(arr[0], arr[1], arr[2], 0.0);
|
||
|
}
|
||
|
pub inline fn loadArr3w(arr: [3]f32, w: f32) F32x4 {
|
||
|
return f32x4(arr[0], arr[1], arr[2], w);
|
||
|
}
|
||
|
pub inline fn loadArr4(arr: [4]f32) F32x4 {
|
||
|
return f32x4(arr[0], arr[1], arr[2], arr[3]);
|
||
|
}
|
||
|
|
||
|
pub inline fn storeArr2(arr: *[2]f32, v: F32x4) void {
|
||
|
arr.* = .{ v[0], v[1] };
|
||
|
}
|
||
|
pub inline fn storeArr3(arr: *[3]f32, v: F32x4) void {
|
||
|
arr.* = .{ v[0], v[1], v[2] };
|
||
|
}
|
||
|
pub inline fn storeArr4(arr: *[4]f32, v: F32x4) void {
|
||
|
arr.* = .{ v[0], v[1], v[2], v[3] };
|
||
|
}
|
||
|
|
||
|
pub inline fn arr3Ptr(ptr: anytype) *const [3]f32 {
|
||
|
comptime assert(@typeInfo(@TypeOf(ptr)) == .Pointer);
|
||
|
const T = std.meta.Child(@TypeOf(ptr));
|
||
|
comptime assert(T == F32x4);
|
||
|
return @as(*const [3]f32, @ptrCast(ptr));
|
||
|
}
|
||
|
|
||
|
pub inline fn arrNPtr(ptr: anytype) [*]const f32 {
|
||
|
comptime assert(@typeInfo(@TypeOf(ptr)) == .Pointer);
|
||
|
const T = std.meta.Child(@TypeOf(ptr));
|
||
|
comptime assert(T == Mat or T == F32x4 or T == F32x8 or T == F32x16);
|
||
|
return @as([*]const f32, @ptrCast(ptr));
|
||
|
}
|
||
|
test "zmath.arrNPtr" {
|
||
|
{
|
||
|
const mat = identity();
|
||
|
const f32ptr = arrNPtr(&mat);
|
||
|
try expect(f32ptr[0] == 1.0);
|
||
|
try expect(f32ptr[5] == 1.0);
|
||
|
try expect(f32ptr[10] == 1.0);
|
||
|
try expect(f32ptr[15] == 1.0);
|
||
|
}
|
||
|
{
|
||
|
const v8 = f32x8s(1.0);
|
||
|
const f32ptr = arrNPtr(&v8);
|
||
|
try expect(f32ptr[1] == 1.0);
|
||
|
try expect(f32ptr[7] == 1.0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
test "zmath.loadArr" {
|
||
|
{
|
||
|
const camera_position = [3]f32{ 1.0, 2.0, 3.0 };
|
||
|
const simd_reg = loadArr3(camera_position);
|
||
|
try expectVecEqual(simd_reg, f32x4(1.0, 2.0, 3.0, 0.0));
|
||
|
}
|
||
|
{
|
||
|
const camera_position = [3]f32{ 1.0, 2.0, 3.0 };
|
||
|
const simd_reg = loadArr3w(camera_position, 1.0);
|
||
|
try expectVecEqual(simd_reg, f32x4(1.0, 2.0, 3.0, 1.0));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn vecToArr2(v: Vec) [2]f32 {
|
||
|
return .{ v[0], v[1] };
|
||
|
}
|
||
|
pub inline fn vecToArr3(v: Vec) [3]f32 {
|
||
|
return .{ v[0], v[1], v[2] };
|
||
|
}
|
||
|
pub inline fn vecToArr4(v: Vec) [4]f32 {
|
||
|
return .{ v[0], v[1], v[2], v[3] };
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 2. Functions that work on all vector components (F32xN = F32x4 or F32x8 or F32x16)
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub fn all(vb: anytype, comptime len: u32) bool {
|
||
|
const T = @TypeOf(vb);
|
||
|
if (len > veclen(T)) {
|
||
|
@compileError("zmath.all(): 'len' is greater than vector len of type " ++ @typeName(T));
|
||
|
}
|
||
|
const loop_len = if (len == 0) veclen(T) else len;
|
||
|
const ab: [veclen(T)]bool = vb;
|
||
|
comptime var i: u32 = 0;
|
||
|
var result = true;
|
||
|
inline while (i < loop_len) : (i += 1) {
|
||
|
result = result and ab[i];
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.all" {
|
||
|
try expect(all(boolx8(true, true, true, true, true, false, true, false), 5) == true);
|
||
|
try expect(all(boolx8(true, true, true, true, true, false, true, false), 6) == false);
|
||
|
try expect(all(boolx8(true, true, true, true, false, false, false, false), 4) == true);
|
||
|
try expect(all(boolx4(true, true, true, false), 3) == true);
|
||
|
try expect(all(boolx4(true, true, true, false), 1) == true);
|
||
|
try expect(all(boolx4(true, false, false, false), 1) == true);
|
||
|
try expect(all(boolx4(false, true, false, false), 1) == false);
|
||
|
try expect(all(boolx8(true, true, true, true, true, false, true, false), 0) == false);
|
||
|
try expect(all(boolx4(false, true, false, false), 0) == false);
|
||
|
try expect(all(boolx4(true, true, true, true), 0) == true);
|
||
|
}
|
||
|
|
||
|
pub fn any(vb: anytype, comptime len: u32) bool {
|
||
|
const T = @TypeOf(vb);
|
||
|
if (len > veclen(T)) {
|
||
|
@compileError("zmath.any(): 'len' is greater than vector len of type " ++ @typeName(T));
|
||
|
}
|
||
|
const loop_len = if (len == 0) veclen(T) else len;
|
||
|
const ab: [veclen(T)]bool = vb;
|
||
|
comptime var i: u32 = 0;
|
||
|
var result = false;
|
||
|
inline while (i < loop_len) : (i += 1) {
|
||
|
result = result or ab[i];
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.any" {
|
||
|
try expect(any(boolx8(true, true, true, true, true, false, true, false), 0) == true);
|
||
|
try expect(any(boolx8(false, false, false, true, true, false, true, false), 3) == false);
|
||
|
try expect(any(boolx8(false, false, false, false, false, true, false, false), 4) == false);
|
||
|
}
|
||
|
|
||
|
pub inline fn isNearEqual(
|
||
|
v0: anytype,
|
||
|
v1: anytype,
|
||
|
epsilon: anytype,
|
||
|
) @Vector(veclen(@TypeOf(v0)), bool) {
|
||
|
const T = @TypeOf(v0, v1, epsilon);
|
||
|
const delta = v0 - v1;
|
||
|
const temp = maxFast(delta, splat(T, 0.0) - delta);
|
||
|
return temp <= epsilon;
|
||
|
}
|
||
|
test "zmath.isNearEqual" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 2.0, -3.0, 4.001);
|
||
|
const v1 = f32x4(1.0, 2.1, 3.0, 4.0);
|
||
|
const b = isNearEqual(v0, v1, splat(F32x4, 0.01));
|
||
|
try expect(@reduce(.And, b == boolx4(true, false, false, true)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(1.0, 2.0, -3.0, 4.001, 1.001, 2.3, -0.0, 0.0);
|
||
|
const v1 = f32x8(1.0, 2.1, 3.0, 4.0, -1.001, 2.1, 0.0, 0.0);
|
||
|
const b = isNearEqual(v0, v1, splat(F32x8, 0.01));
|
||
|
try expect(@reduce(.And, b == boolx8(true, false, false, true, false, false, true, true)));
|
||
|
}
|
||
|
try expect(all(isNearEqual(
|
||
|
splat(F32x4, math.inf(f32)),
|
||
|
splat(F32x4, math.inf(f32)),
|
||
|
splat(F32x4, 0.0001),
|
||
|
), 0) == false);
|
||
|
try expect(all(isNearEqual(
|
||
|
splat(F32x4, -math.inf(f32)),
|
||
|
splat(F32x4, math.inf(f32)),
|
||
|
splat(F32x4, 0.0001),
|
||
|
), 0) == false);
|
||
|
try expect(all(isNearEqual(
|
||
|
splat(F32x4, -math.inf(f32)),
|
||
|
splat(F32x4, -math.inf(f32)),
|
||
|
splat(F32x4, 0.0001),
|
||
|
), 0) == false);
|
||
|
try expect(all(isNearEqual(
|
||
|
splat(F32x4, -math.nan(f32)),
|
||
|
splat(F32x4, math.inf(f32)),
|
||
|
splat(F32x4, 0.0001),
|
||
|
), 0) == false);
|
||
|
}
|
||
|
|
||
|
pub inline fn isNan(
|
||
|
v: anytype,
|
||
|
) @Vector(veclen(@TypeOf(v)), bool) {
|
||
|
return v != v;
|
||
|
}
|
||
|
test "zmath.isNan" {
|
||
|
{
|
||
|
const v0 = f32x4(math.inf(f32), math.nan(f32), math.nan(f32), 7.0);
|
||
|
const b = isNan(v0);
|
||
|
try expect(@reduce(.And, b == boolx4(false, true, true, false)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, math.nan(f32), 0, 0, math.inf(f32), math.nan(f32), math.snan(f32), 7.0);
|
||
|
const b = isNan(v0);
|
||
|
try expect(@reduce(.And, b == boolx8(false, true, false, false, false, true, true, false)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn isInf(
|
||
|
v: anytype,
|
||
|
) @Vector(veclen(@TypeOf(v)), bool) {
|
||
|
const T = @TypeOf(v);
|
||
|
return abs(v) == splat(T, math.inf(f32));
|
||
|
}
|
||
|
test "zmath.isInf" {
|
||
|
{
|
||
|
const v0 = f32x4(math.inf(f32), math.nan(f32), math.snan(f32), 7.0);
|
||
|
const b = isInf(v0);
|
||
|
try expect(@reduce(.And, b == boolx4(true, false, false, false)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, math.inf(f32), 0, 0, math.inf(f32), math.nan(f32), math.snan(f32), 7.0);
|
||
|
const b = isInf(v0);
|
||
|
try expect(@reduce(.And, b == boolx8(false, true, false, false, true, false, false, false)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn isInBounds(
|
||
|
v: anytype,
|
||
|
bounds: anytype,
|
||
|
) @Vector(veclen(@TypeOf(v)), bool) {
|
||
|
const T = @TypeOf(v, bounds);
|
||
|
const Tu = @Vector(veclen(T), u1);
|
||
|
const Tr = @Vector(veclen(T), bool);
|
||
|
|
||
|
// 2 x cmpleps, xorps, load, andps
|
||
|
const b0 = v <= bounds;
|
||
|
const b1 = (bounds * splat(T, -1.0)) <= v;
|
||
|
const b0u = @as(Tu, @bitCast(b0));
|
||
|
const b1u = @as(Tu, @bitCast(b1));
|
||
|
return @as(Tr, @bitCast(b0u & b1u));
|
||
|
}
|
||
|
test "zmath.isInBounds" {
|
||
|
{
|
||
|
const v0 = f32x4(0.5, -2.0, -1.0, 1.9);
|
||
|
const v1 = f32x4(-1.6, -2.001, -1.0, 1.9);
|
||
|
const bounds = f32x4(1.0, 2.0, 1.0, 2.0);
|
||
|
const b0 = isInBounds(v0, bounds);
|
||
|
const b1 = isInBounds(v1, bounds);
|
||
|
try expect(@reduce(.And, b0 == boolx4(true, true, true, true)));
|
||
|
try expect(@reduce(.And, b1 == boolx4(false, false, true, true)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(2.0, 1.0, 2.0, 1.0, 0.5, -2.0, -1.0, 1.9);
|
||
|
const bounds = f32x8(1.0, 1.0, 1.0, math.inf(f32), 1.0, math.nan(f32), 1.0, 2.0);
|
||
|
const b0 = isInBounds(v0, bounds);
|
||
|
try expect(@reduce(.And, b0 == boolx8(false, true, false, true, true, false, true, true)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn andInt(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
const Tu = @Vector(veclen(T), u32);
|
||
|
const v0u = @as(Tu, @bitCast(v0));
|
||
|
const v1u = @as(Tu, @bitCast(v1));
|
||
|
return @as(T, @bitCast(v0u & v1u)); // andps
|
||
|
}
|
||
|
test "zmath.andInt" {
|
||
|
{
|
||
|
const v0 = f32x4(0, @as(f32, @bitCast(~@as(u32, 0))), 0, @as(f32, @bitCast(~@as(u32, 0))));
|
||
|
const v1 = f32x4(1.0, 2.0, 3.0, math.inf(f32));
|
||
|
const v = andInt(v0, v1);
|
||
|
try expect(v[3] == math.inf(f32));
|
||
|
try expectVecEqual(v, f32x4(0.0, 2.0, 0.0, math.inf(f32)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, 0, 0, 0, 0, @as(f32, @bitCast(~@as(u32, 0))), 0, @as(f32, @bitCast(~@as(u32, 0))));
|
||
|
const v1 = f32x8(0, 0, 0, 0, 1.0, 2.0, 3.0, math.inf(f32));
|
||
|
const v = andInt(v0, v1);
|
||
|
try expect(v[7] == math.inf(f32));
|
||
|
try expectVecEqual(v, f32x8(0, 0, 0, 0, 0.0, 2.0, 0.0, math.inf(f32)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn andNotInt(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
const Tu = @Vector(veclen(T), u32);
|
||
|
const v0u = @as(Tu, @bitCast(v0));
|
||
|
const v1u = @as(Tu, @bitCast(v1));
|
||
|
return @as(T, @bitCast(~v0u & v1u)); // andnps
|
||
|
}
|
||
|
test "zmath.andNotInt" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 2.0, 3.0, 4.0);
|
||
|
const v1 = f32x4(0, @as(f32, @bitCast(~@as(u32, 0))), 0, @as(f32, @bitCast(~@as(u32, 0))));
|
||
|
const v = andNotInt(v1, v0);
|
||
|
try expectVecEqual(v, f32x4(1.0, 0.0, 3.0, 0.0));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, 0, 0, 0, 1.0, 2.0, 3.0, 4.0);
|
||
|
const v1 = f32x8(0, 0, 0, 0, 0, @as(f32, @bitCast(~@as(u32, 0))), 0, @as(f32, @bitCast(~@as(u32, 0))));
|
||
|
const v = andNotInt(v1, v0);
|
||
|
try expectVecEqual(v, f32x8(0, 0, 0, 0, 1.0, 0.0, 3.0, 0.0));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn orInt(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
const Tu = @Vector(veclen(T), u32);
|
||
|
const v0u = @as(Tu, @bitCast(v0));
|
||
|
const v1u = @as(Tu, @bitCast(v1));
|
||
|
return @as(T, @bitCast(v0u | v1u)); // orps
|
||
|
}
|
||
|
test "zmath.orInt" {
|
||
|
{
|
||
|
const v0 = f32x4(0, @as(f32, @bitCast(~@as(u32, 0))), 0, 0);
|
||
|
const v1 = f32x4(1.0, 2.0, 3.0, 4.0);
|
||
|
const v = orInt(v0, v1);
|
||
|
try expect(v[0] == 1.0);
|
||
|
try expect(@as(u32, @bitCast(v[1])) == ~@as(u32, 0));
|
||
|
try expect(v[2] == 3.0);
|
||
|
try expect(v[3] == 4.0);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, 0, 0, 0, 0, @as(f32, @bitCast(~@as(u32, 0))), 0, 0);
|
||
|
const v1 = f32x8(0, 0, 0, 0, 1.0, 2.0, 3.0, 4.0);
|
||
|
const v = orInt(v0, v1);
|
||
|
try expect(v[4] == 1.0);
|
||
|
try expect(@as(u32, @bitCast(v[5])) == ~@as(u32, 0));
|
||
|
try expect(v[6] == 3.0);
|
||
|
try expect(v[7] == 4.0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn norInt(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
const Tu = @Vector(veclen(T), u32);
|
||
|
const v0u = @as(Tu, @bitCast(v0));
|
||
|
const v1u = @as(Tu, @bitCast(v1));
|
||
|
return @as(T, @bitCast(~(v0u | v1u))); // por, pcmpeqd, pxor
|
||
|
}
|
||
|
|
||
|
pub inline fn xorInt(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
const Tu = @Vector(veclen(T), u32);
|
||
|
const v0u = @as(Tu, @bitCast(v0));
|
||
|
const v1u = @as(Tu, @bitCast(v1));
|
||
|
return @as(T, @bitCast(v0u ^ v1u)); // xorps
|
||
|
}
|
||
|
test "zmath.xorInt" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, @as(f32, @bitCast(~@as(u32, 0))), 0, 0);
|
||
|
const v1 = f32x4(1.0, 0, 0, 0);
|
||
|
const v = xorInt(v0, v1);
|
||
|
try expect(v[0] == 0.0);
|
||
|
try expect(@as(u32, @bitCast(v[1])) == ~@as(u32, 0));
|
||
|
try expect(v[2] == 0.0);
|
||
|
try expect(v[3] == 0.0);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, 0, 0, 0, 1.0, @as(f32, @bitCast(~@as(u32, 0))), 0, 0);
|
||
|
const v1 = f32x8(0, 0, 0, 0, 1.0, 0, 0, 0);
|
||
|
const v = xorInt(v0, v1);
|
||
|
try expect(v[4] == 0.0);
|
||
|
try expect(@as(u32, @bitCast(v[5])) == ~@as(u32, 0));
|
||
|
try expect(v[6] == 0.0);
|
||
|
try expect(v[7] == 0.0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn minFast(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
return select(v0 < v1, v0, v1); // minps
|
||
|
}
|
||
|
test "zmath.minFast" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 3.0, 2.0, 7.0);
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = minFast(v0, v1);
|
||
|
try expectVecEqual(v, f32x4(1.0, 1.0, 2.0, 7.0));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(1.0, math.nan(f32), 5.0, math.snan(f32));
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = minFast(v0, v1);
|
||
|
try expect(v[0] == 1.0);
|
||
|
try expect(v[1] == 1.0);
|
||
|
try expect(!math.isNan(v[1]));
|
||
|
try expect(v[2] == 4.0);
|
||
|
try expect(v[3] == math.inf(f32));
|
||
|
try expect(!math.isNan(v[3]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn maxFast(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
return select(v0 > v1, v0, v1); // maxps
|
||
|
}
|
||
|
test "zmath.maxFast" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 3.0, 2.0, 7.0);
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = maxFast(v0, v1);
|
||
|
try expectVecEqual(v, f32x4(2.0, 3.0, 4.0, math.inf(f32)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(1.0, math.nan(f32), 5.0, math.snan(f32));
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = maxFast(v0, v1);
|
||
|
try expect(v[0] == 2.0);
|
||
|
try expect(v[1] == 1.0);
|
||
|
try expect(v[2] == 5.0);
|
||
|
try expect(v[3] == math.inf(f32));
|
||
|
try expect(!math.isNan(v[3]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn min(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
// This will handle inf & nan
|
||
|
return @min(v0, v1); // minps, cmpunordps, andps, andnps, orps
|
||
|
}
|
||
|
test "zmath.min" {
|
||
|
// Calling math.inf causes test to fail!
|
||
|
if (builtin.target.os.tag == .macos and builtin.target.cpu.arch == .aarch64) return error.SkipZigTest;
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 3.0, 2.0, 7.0);
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = min(v0, v1);
|
||
|
try expectVecEqual(v, f32x4(1.0, 1.0, 2.0, 7.0));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, 0, -2.0, 0, 1.0, 3.0, 2.0, 7.0);
|
||
|
const v1 = f32x8(0, 1.0, 0, 0, 2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = min(v0, v1);
|
||
|
try expectVecEqual(v, f32x8(0.0, 0.0, -2.0, 0.0, 1.0, 1.0, 2.0, 7.0));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(1.0, math.nan(f32), 5.0, math.snan(f32));
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = min(v0, v1);
|
||
|
try expect(v[0] == 1.0);
|
||
|
try expect(v[1] == 1.0);
|
||
|
try expect(!math.isNan(v[1]));
|
||
|
try expect(v[2] == 4.0);
|
||
|
try expect(v[3] == math.inf(f32));
|
||
|
try expect(!math.isNan(v[3]));
|
||
|
}
|
||
|
|
||
|
{
|
||
|
const v0 = f32x4(-math.inf(f32), math.inf(f32), math.inf(f32), math.snan(f32));
|
||
|
const v1 = f32x4(math.snan(f32), -math.inf(f32), math.snan(f32), math.nan(f32));
|
||
|
const v = min(v0, v1);
|
||
|
try expect(v[0] == -math.inf(f32));
|
||
|
try expect(v[1] == -math.inf(f32));
|
||
|
try expect(v[2] == math.inf(f32));
|
||
|
try expect(!math.isNan(v[2]));
|
||
|
try expect(math.isNan(v[3]));
|
||
|
try expect(!math.isInf(v[3]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn max(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
// This will handle inf & nan
|
||
|
return @max(v0, v1); // maxps, cmpunordps, andps, andnps, orps
|
||
|
}
|
||
|
test "zmath.max" {
|
||
|
// Calling math.inf causes test to fail!
|
||
|
if (builtin.target.os.tag == .macos and builtin.target.cpu.arch == .aarch64) return error.SkipZigTest;
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 3.0, 2.0, 7.0);
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = max(v0, v1);
|
||
|
try expectVecEqual(v, f32x4(2.0, 3.0, 4.0, math.inf(f32)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0, 0, -2.0, 0, 1.0, 3.0, 2.0, 7.0);
|
||
|
const v1 = f32x8(0, 1.0, 0, 0, 2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = max(v0, v1);
|
||
|
try expectVecEqual(v, f32x8(0.0, 1.0, 0.0, 0.0, 2.0, 3.0, 4.0, math.inf(f32)));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(1.0, math.nan(f32), 5.0, math.snan(f32));
|
||
|
const v1 = f32x4(2.0, 1.0, 4.0, math.inf(f32));
|
||
|
const v = max(v0, v1);
|
||
|
try expect(v[0] == 2.0);
|
||
|
try expect(v[1] == 1.0);
|
||
|
try expect(v[2] == 5.0);
|
||
|
try expect(v[3] == math.inf(f32));
|
||
|
try expect(!math.isNan(v[3]));
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(-math.inf(f32), math.inf(f32), math.inf(f32), math.snan(f32));
|
||
|
const v1 = f32x4(math.snan(f32), -math.inf(f32), math.snan(f32), math.nan(f32));
|
||
|
const v = max(v0, v1);
|
||
|
try expect(v[0] == -math.inf(f32));
|
||
|
try expect(v[1] == math.inf(f32));
|
||
|
try expect(v[2] == math.inf(f32));
|
||
|
try expect(!math.isNan(v[2]));
|
||
|
try expect(math.isNan(v[3]));
|
||
|
try expect(!math.isInf(v[3]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn round(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
if (cpu_arch == .x86_64 and has_avx) {
|
||
|
if (T == F32x4) {
|
||
|
return asm ("vroundps $0, %%xmm0, %%xmm0"
|
||
|
: [ret] "={xmm0}" (-> T),
|
||
|
: [v] "{xmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x8) {
|
||
|
return asm ("vroundps $0, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> T),
|
||
|
: [v] "{ymm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and has_avx512f) {
|
||
|
return asm ("vrndscaleps $0, %%zmm0, %%zmm0"
|
||
|
: [ret] "={zmm0}" (-> T),
|
||
|
: [v] "{zmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and !has_avx512f) {
|
||
|
const arr: [16]f32 = v;
|
||
|
var ymm0 = @as(F32x8, arr[0..8].*);
|
||
|
var ymm1 = @as(F32x8, arr[8..16].*);
|
||
|
ymm0 = asm ("vroundps $0, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> F32x8),
|
||
|
: [v] "{ymm0}" (ymm0),
|
||
|
);
|
||
|
ymm1 = asm ("vroundps $0, %%ymm1, %%ymm1"
|
||
|
: [ret] "={ymm1}" (-> F32x8),
|
||
|
: [v] "{ymm1}" (ymm1),
|
||
|
);
|
||
|
return @shuffle(f32, ymm0, ymm1, [16]i32{ 0, 1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, -7, -8 });
|
||
|
}
|
||
|
} else {
|
||
|
const sign = andInt(v, splatNegativeZero(T));
|
||
|
const magic = orInt(splatNoFraction(T), sign);
|
||
|
var r1 = v + magic;
|
||
|
r1 = r1 - magic;
|
||
|
const r2 = abs(v);
|
||
|
const mask = r2 <= splatNoFraction(T);
|
||
|
return select(mask, r1, v);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.round" {
|
||
|
{
|
||
|
try expect(all(round(splat(F32x4, math.inf(f32))) == splat(F32x4, math.inf(f32)), 0));
|
||
|
try expect(all(round(splat(F32x4, -math.inf(f32))) == splat(F32x4, -math.inf(f32)), 0));
|
||
|
try expect(all(isNan(round(splat(F32x4, math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(round(splat(F32x4, -math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(round(splat(F32x4, math.snan(f32)))), 0));
|
||
|
try expect(all(isNan(round(splat(F32x4, -math.snan(f32)))), 0));
|
||
|
}
|
||
|
{
|
||
|
const v = round(f32x16(1.1, -1.1, -1.5, 1.5, 2.1, 2.8, 2.9, 4.1, 5.8, 6.1, 7.9, 8.9, 10.1, 11.2, 12.7, 13.1));
|
||
|
try expectVecApproxEqAbs(
|
||
|
v,
|
||
|
f32x16(1.0, -1.0, -2.0, 2.0, 2.0, 3.0, 3.0, 4.0, 6.0, 6.0, 8.0, 9.0, 10.0, 11.0, 13.0, 13.0),
|
||
|
0.0,
|
||
|
);
|
||
|
}
|
||
|
var v = round(f32x4(1.1, -1.1, -1.5, 1.5));
|
||
|
try expectVecEqual(v, f32x4(1.0, -1.0, -2.0, 2.0));
|
||
|
|
||
|
const v1 = f32x4(-10_000_000.1, -math.inf(f32), 10_000_001.5, math.inf(f32));
|
||
|
v = round(v1);
|
||
|
try expect(v[3] == math.inf(f32));
|
||
|
try expectVecEqual(v, f32x4(-10_000_000.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
|
||
|
const v2 = f32x4(-math.snan(f32), math.snan(f32), math.nan(f32), -math.inf(f32));
|
||
|
v = round(v2);
|
||
|
try expect(math.isNan(v2[0]));
|
||
|
try expect(math.isNan(v2[1]));
|
||
|
try expect(math.isNan(v2[2]));
|
||
|
try expect(v2[3] == -math.inf(f32));
|
||
|
|
||
|
const v3 = f32x4(1001.5, -201.499, -10000.99, -101.5);
|
||
|
v = round(v3);
|
||
|
try expectVecEqual(v, f32x4(1002.0, -201.0, -10001.0, -102.0));
|
||
|
|
||
|
const v4 = f32x4(-1_388_609.9, 1_388_609.5, 1_388_109.01, 2_388_609.5);
|
||
|
v = round(v4);
|
||
|
try expectVecEqual(v, f32x4(-1_388_610.0, 1_388_610.0, 1_388_109.0, 2_388_610.0));
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const vr = round(splat(F32x4, f));
|
||
|
const fr = @round(splat(F32x4, f));
|
||
|
const vr8 = round(splat(F32x8, f));
|
||
|
const fr8 = @round(splat(F32x8, f));
|
||
|
const vr16 = round(splat(F32x16, f));
|
||
|
const fr16 = @round(splat(F32x16, f));
|
||
|
try expectVecEqual(vr, fr);
|
||
|
try expectVecEqual(vr8, fr8);
|
||
|
try expectVecEqual(vr16, fr16);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn trunc(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
if (cpu_arch == .x86_64 and has_avx) {
|
||
|
if (T == F32x4) {
|
||
|
return asm ("vroundps $3, %%xmm0, %%xmm0"
|
||
|
: [ret] "={xmm0}" (-> T),
|
||
|
: [v] "{xmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x8) {
|
||
|
return asm ("vroundps $3, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> T),
|
||
|
: [v] "{ymm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and has_avx512f) {
|
||
|
return asm ("vrndscaleps $3, %%zmm0, %%zmm0"
|
||
|
: [ret] "={zmm0}" (-> T),
|
||
|
: [v] "{zmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and !has_avx512f) {
|
||
|
const arr: [16]f32 = v;
|
||
|
var ymm0 = @as(F32x8, arr[0..8].*);
|
||
|
var ymm1 = @as(F32x8, arr[8..16].*);
|
||
|
ymm0 = asm ("vroundps $3, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> F32x8),
|
||
|
: [v] "{ymm0}" (ymm0),
|
||
|
);
|
||
|
ymm1 = asm ("vroundps $3, %%ymm1, %%ymm1"
|
||
|
: [ret] "={ymm1}" (-> F32x8),
|
||
|
: [v] "{ymm1}" (ymm1),
|
||
|
);
|
||
|
return @shuffle(f32, ymm0, ymm1, [16]i32{ 0, 1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, -7, -8 });
|
||
|
}
|
||
|
} else {
|
||
|
const mask = abs(v) < splatNoFraction(T);
|
||
|
const result = floatToIntAndBack(v);
|
||
|
return select(mask, result, v);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.trunc" {
|
||
|
{
|
||
|
try expect(all(trunc(splat(F32x4, math.inf(f32))) == splat(F32x4, math.inf(f32)), 0));
|
||
|
try expect(all(trunc(splat(F32x4, -math.inf(f32))) == splat(F32x4, -math.inf(f32)), 0));
|
||
|
try expect(all(isNan(trunc(splat(F32x4, math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(trunc(splat(F32x4, -math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(trunc(splat(F32x4, math.snan(f32)))), 0));
|
||
|
try expect(all(isNan(trunc(splat(F32x4, -math.snan(f32)))), 0));
|
||
|
}
|
||
|
{
|
||
|
const v = trunc(f32x16(1.1, -1.1, -1.5, 1.5, 2.1, 2.8, 2.9, 4.1, 5.8, 6.1, 7.9, 8.9, 10.1, 11.2, 12.7, 13.1));
|
||
|
try expectVecApproxEqAbs(
|
||
|
v,
|
||
|
f32x16(1.0, -1.0, -1.0, 1.0, 2.0, 2.0, 2.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 11.0, 12.0, 13.0),
|
||
|
0.0,
|
||
|
);
|
||
|
}
|
||
|
var v = trunc(f32x4(1.1, -1.1, -1.5, 1.5));
|
||
|
try expectVecEqual(v, f32x4(1.0, -1.0, -1.0, 1.0));
|
||
|
|
||
|
v = trunc(f32x4(-10_000_002.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
try expectVecEqual(v, f32x4(-10_000_002.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
|
||
|
v = trunc(f32x4(-math.snan(f32), math.snan(f32), math.nan(f32), -math.inf(f32)));
|
||
|
try expect(math.isNan(v[0]));
|
||
|
try expect(math.isNan(v[1]));
|
||
|
try expect(math.isNan(v[2]));
|
||
|
try expect(v[3] == -math.inf(f32));
|
||
|
|
||
|
v = trunc(f32x4(1000.5001, -201.499, -10000.99, 100.750001));
|
||
|
try expectVecEqual(v, f32x4(1000.0, -201.0, -10000.0, 100.0));
|
||
|
|
||
|
v = trunc(f32x4(-7_388_609.5, 7_388_609.1, 8_388_109.5, -8_388_509.5));
|
||
|
try expectVecEqual(v, f32x4(-7_388_609.0, 7_388_609.0, 8_388_109.0, -8_388_509.0));
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const vr = trunc(splat(F32x4, f));
|
||
|
const fr = @trunc(splat(F32x4, f));
|
||
|
const vr8 = trunc(splat(F32x8, f));
|
||
|
const fr8 = @trunc(splat(F32x8, f));
|
||
|
const vr16 = trunc(splat(F32x16, f));
|
||
|
const fr16 = @trunc(splat(F32x16, f));
|
||
|
try expectVecEqual(vr, fr);
|
||
|
try expectVecEqual(vr8, fr8);
|
||
|
try expectVecEqual(vr16, fr16);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn floor(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
if (cpu_arch == .x86_64 and has_avx) {
|
||
|
if (T == F32x4) {
|
||
|
return asm ("vroundps $1, %%xmm0, %%xmm0"
|
||
|
: [ret] "={xmm0}" (-> T),
|
||
|
: [v] "{xmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x8) {
|
||
|
return asm ("vroundps $1, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> T),
|
||
|
: [v] "{ymm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and has_avx512f) {
|
||
|
return asm ("vrndscaleps $1, %%zmm0, %%zmm0"
|
||
|
: [ret] "={zmm0}" (-> T),
|
||
|
: [v] "{zmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and !has_avx512f) {
|
||
|
const arr: [16]f32 = v;
|
||
|
var ymm0 = @as(F32x8, arr[0..8].*);
|
||
|
var ymm1 = @as(F32x8, arr[8..16].*);
|
||
|
ymm0 = asm ("vroundps $1, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> F32x8),
|
||
|
: [v] "{ymm0}" (ymm0),
|
||
|
);
|
||
|
ymm1 = asm ("vroundps $1, %%ymm1, %%ymm1"
|
||
|
: [ret] "={ymm1}" (-> F32x8),
|
||
|
: [v] "{ymm1}" (ymm1),
|
||
|
);
|
||
|
return @shuffle(f32, ymm0, ymm1, [16]i32{ 0, 1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, -7, -8 });
|
||
|
}
|
||
|
} else {
|
||
|
const mask = abs(v) < splatNoFraction(T);
|
||
|
var result = floatToIntAndBack(v);
|
||
|
const larger_mask = result > v;
|
||
|
const larger = select(larger_mask, splat(T, -1.0), splat(T, 0.0));
|
||
|
result = result + larger;
|
||
|
return select(mask, result, v);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.floor" {
|
||
|
{
|
||
|
try expect(all(floor(splat(F32x4, math.inf(f32))) == splat(F32x4, math.inf(f32)), 0));
|
||
|
try expect(all(floor(splat(F32x4, -math.inf(f32))) == splat(F32x4, -math.inf(f32)), 0));
|
||
|
try expect(all(isNan(floor(splat(F32x4, math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(floor(splat(F32x4, -math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(floor(splat(F32x4, math.snan(f32)))), 0));
|
||
|
try expect(all(isNan(floor(splat(F32x4, -math.snan(f32)))), 0));
|
||
|
}
|
||
|
{
|
||
|
const v = floor(f32x16(1.1, -1.1, -1.5, 1.5, 2.1, 2.8, 2.9, 4.1, 5.8, 6.1, 7.9, 8.9, 10.1, 11.2, 12.7, 13.1));
|
||
|
try expectVecApproxEqAbs(
|
||
|
v,
|
||
|
f32x16(1.0, -2.0, -2.0, 1.0, 2.0, 2.0, 2.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 11.0, 12.0, 13.0),
|
||
|
0.0,
|
||
|
);
|
||
|
}
|
||
|
var v = floor(f32x4(1.5, -1.5, -1.7, -2.1));
|
||
|
try expectVecEqual(v, f32x4(1.0, -2.0, -2.0, -3.0));
|
||
|
|
||
|
v = floor(f32x4(-10_000_002.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
try expectVecEqual(v, f32x4(-10_000_002.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
|
||
|
v = floor(f32x4(-math.snan(f32), math.snan(f32), math.nan(f32), -math.inf(f32)));
|
||
|
try expect(math.isNan(v[0]));
|
||
|
try expect(math.isNan(v[1]));
|
||
|
try expect(math.isNan(v[2]));
|
||
|
try expect(v[3] == -math.inf(f32));
|
||
|
|
||
|
v = floor(f32x4(1000.5001, -201.499, -10000.99, 100.75001));
|
||
|
try expectVecEqual(v, f32x4(1000.0, -202.0, -10001.0, 100.0));
|
||
|
|
||
|
v = floor(f32x4(-7_388_609.5, 7_388_609.1, 8_388_109.5, -8_388_509.5));
|
||
|
try expectVecEqual(v, f32x4(-7_388_610.0, 7_388_609.0, 8_388_109.0, -8_388_510.0));
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const vr = floor(splat(F32x4, f));
|
||
|
const fr = @floor(splat(F32x4, f));
|
||
|
const vr8 = floor(splat(F32x8, f));
|
||
|
const fr8 = @floor(splat(F32x8, f));
|
||
|
const vr16 = floor(splat(F32x16, f));
|
||
|
const fr16 = @floor(splat(F32x16, f));
|
||
|
try expectVecEqual(vr, fr);
|
||
|
try expectVecEqual(vr8, fr8);
|
||
|
try expectVecEqual(vr16, fr16);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn ceil(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
if (cpu_arch == .x86_64 and has_avx) {
|
||
|
if (T == F32x4) {
|
||
|
return asm ("vroundps $2, %%xmm0, %%xmm0"
|
||
|
: [ret] "={xmm0}" (-> T),
|
||
|
: [v] "{xmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x8) {
|
||
|
return asm ("vroundps $2, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> T),
|
||
|
: [v] "{ymm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and has_avx512f) {
|
||
|
return asm ("vrndscaleps $2, %%zmm0, %%zmm0"
|
||
|
: [ret] "={zmm0}" (-> T),
|
||
|
: [v] "{zmm0}" (v),
|
||
|
);
|
||
|
} else if (T == F32x16 and !has_avx512f) {
|
||
|
const arr: [16]f32 = v;
|
||
|
var ymm0 = @as(F32x8, arr[0..8].*);
|
||
|
var ymm1 = @as(F32x8, arr[8..16].*);
|
||
|
ymm0 = asm ("vroundps $2, %%ymm0, %%ymm0"
|
||
|
: [ret] "={ymm0}" (-> F32x8),
|
||
|
: [v] "{ymm0}" (ymm0),
|
||
|
);
|
||
|
ymm1 = asm ("vroundps $2, %%ymm1, %%ymm1"
|
||
|
: [ret] "={ymm1}" (-> F32x8),
|
||
|
: [v] "{ymm1}" (ymm1),
|
||
|
);
|
||
|
return @shuffle(f32, ymm0, ymm1, [16]i32{ 0, 1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, -7, -8 });
|
||
|
}
|
||
|
} else {
|
||
|
const mask = abs(v) < splatNoFraction(T);
|
||
|
var result = floatToIntAndBack(v);
|
||
|
const smaller_mask = result < v;
|
||
|
const smaller = select(smaller_mask, splat(T, -1.0), splat(T, 0.0));
|
||
|
result = result - smaller;
|
||
|
return select(mask, result, v);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.ceil" {
|
||
|
{
|
||
|
try expect(all(ceil(splat(F32x4, math.inf(f32))) == splat(F32x4, math.inf(f32)), 0));
|
||
|
try expect(all(ceil(splat(F32x4, -math.inf(f32))) == splat(F32x4, -math.inf(f32)), 0));
|
||
|
try expect(all(isNan(ceil(splat(F32x4, math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(ceil(splat(F32x4, -math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(ceil(splat(F32x4, math.snan(f32)))), 0));
|
||
|
try expect(all(isNan(ceil(splat(F32x4, -math.snan(f32)))), 0));
|
||
|
}
|
||
|
{
|
||
|
const v = ceil(f32x16(1.1, -1.1, -1.5, 1.5, 2.1, 2.8, 2.9, 4.1, 5.8, 6.1, 7.9, 8.9, 10.1, 11.2, 12.7, 13.1));
|
||
|
try expectVecApproxEqAbs(
|
||
|
v,
|
||
|
f32x16(2.0, -1.0, -1.0, 2.0, 3.0, 3.0, 3.0, 5.0, 6.0, 7.0, 8.0, 9.0, 11.0, 12.0, 13.0, 14.0),
|
||
|
0.0,
|
||
|
);
|
||
|
}
|
||
|
var v = ceil(f32x4(1.5, -1.5, -1.7, -2.1));
|
||
|
try expectVecEqual(v, f32x4(2.0, -1.0, -1.0, -2.0));
|
||
|
|
||
|
v = ceil(f32x4(-10_000_002.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
try expectVecEqual(v, f32x4(-10_000_002.1, -math.inf(f32), 10_000_001.5, math.inf(f32)));
|
||
|
|
||
|
v = ceil(f32x4(-math.snan(f32), math.snan(f32), math.nan(f32), -math.inf(f32)));
|
||
|
try expect(math.isNan(v[0]));
|
||
|
try expect(math.isNan(v[1]));
|
||
|
try expect(math.isNan(v[2]));
|
||
|
try expect(v[3] == -math.inf(f32));
|
||
|
|
||
|
v = ceil(f32x4(1000.5001, -201.499, -10000.99, 100.75001));
|
||
|
try expectVecEqual(v, f32x4(1001.0, -201.0, -10000.0, 101.0));
|
||
|
|
||
|
v = ceil(f32x4(-1_388_609.5, 1_388_609.1, 1_388_109.9, -1_388_509.9));
|
||
|
try expectVecEqual(v, f32x4(-1_388_609.0, 1_388_610.0, 1_388_110.0, -1_388_509.0));
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const vr = ceil(splat(F32x4, f));
|
||
|
const fr = @ceil(splat(F32x4, f));
|
||
|
const vr8 = ceil(splat(F32x8, f));
|
||
|
const fr8 = @ceil(splat(F32x8, f));
|
||
|
const vr16 = ceil(splat(F32x16, f));
|
||
|
const fr16 = @ceil(splat(F32x16, f));
|
||
|
try expectVecEqual(vr, fr);
|
||
|
try expectVecEqual(vr8, fr8);
|
||
|
try expectVecEqual(vr16, fr16);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn clamp(v: anytype, vmin: anytype, vmax: anytype) @TypeOf(v, vmin, vmax) {
|
||
|
var result = max(vmin, v);
|
||
|
result = min(vmax, result);
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.clamp" {
|
||
|
// Calling math.inf causes test to fail!
|
||
|
if (builtin.target.os.tag == .macos and builtin.target.cpu.arch == .aarch64) return error.SkipZigTest;
|
||
|
{
|
||
|
const v0 = f32x4(-1.0, 0.2, 1.1, -0.3);
|
||
|
const v = clamp(v0, splat(F32x4, -0.5), splat(F32x4, 0.5));
|
||
|
try expectVecApproxEqAbs(v, f32x4(-0.5, 0.2, 0.5, -0.3), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(-2.0, 0.25, -0.25, 100.0, -1.0, 0.2, 1.1, -0.3);
|
||
|
const v = clamp(v0, splat(F32x8, -0.5), splat(F32x8, 0.5));
|
||
|
try expectVecApproxEqAbs(v, f32x8(-0.5, 0.25, -0.25, 0.5, -0.5, 0.2, 0.5, -0.3), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(-math.inf(f32), math.inf(f32), math.nan(f32), math.snan(f32));
|
||
|
const v = clamp(v0, f32x4(-100.0, 0.0, -100.0, 0.0), f32x4(0.0, 100.0, 0.0, 100.0));
|
||
|
try expectVecApproxEqAbs(v, f32x4(-100.0, 100.0, -100.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(math.inf(f32), math.inf(f32), -math.nan(f32), -math.snan(f32));
|
||
|
const v = clamp(v0, splat(F32x4, -1.0), splat(F32x4, 1.0));
|
||
|
try expectVecApproxEqAbs(v, f32x4(1.0, 1.0, -1.0, -1.0), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn clampFast(v: anytype, vmin: anytype, vmax: anytype) @TypeOf(v, vmin, vmax) {
|
||
|
var result = maxFast(vmin, v);
|
||
|
result = minFast(vmax, result);
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.clampFast" {
|
||
|
{
|
||
|
const v0 = f32x4(-1.0, 0.2, 1.1, -0.3);
|
||
|
const v = clampFast(v0, splat(F32x4, -0.5), splat(F32x4, 0.5));
|
||
|
try expectVecApproxEqAbs(v, f32x4(-0.5, 0.2, 0.5, -0.3), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn saturate(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
var result = max(v, splat(T, 0.0));
|
||
|
result = min(result, splat(T, 1.0));
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.saturate" {
|
||
|
// Calling math.inf causes test to fail!
|
||
|
if (builtin.target.os.tag == .macos and builtin.target.cpu.arch == .aarch64) return error.SkipZigTest;
|
||
|
{
|
||
|
const v0 = f32x4(-1.0, 0.2, 1.1, -0.3);
|
||
|
const v = saturate(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x4(0.0, 0.2, 1.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0.0, 0.0, 2.0, -2.0, -1.0, 0.2, 1.1, -0.3);
|
||
|
const v = saturate(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x8(0.0, 0.0, 1.0, 0.0, 0.0, 0.2, 1.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(-math.inf(f32), math.inf(f32), math.nan(f32), math.snan(f32));
|
||
|
const v = saturate(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x4(0.0, 1.0, 0.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(math.inf(f32), math.inf(f32), -math.nan(f32), -math.snan(f32));
|
||
|
const v = saturate(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x4(1.0, 1.0, 0.0, 0.0), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn saturateFast(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
var result = maxFast(v, splat(T, 0.0));
|
||
|
result = minFast(result, splat(T, 1.0));
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.saturateFast" {
|
||
|
{
|
||
|
const v0 = f32x4(-1.0, 0.2, 1.1, -0.3);
|
||
|
const v = saturateFast(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x4(0.0, 0.2, 1.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x8(0.0, 0.0, 2.0, -2.0, -1.0, 0.2, 1.1, -0.3);
|
||
|
const v = saturateFast(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x8(0.0, 0.0, 1.0, 0.0, 0.0, 0.2, 1.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(-math.inf(f32), math.inf(f32), math.nan(f32), math.snan(f32));
|
||
|
const v = saturateFast(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x4(0.0, 1.0, 0.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(math.inf(f32), math.inf(f32), -math.nan(f32), -math.snan(f32));
|
||
|
const v = saturateFast(v0);
|
||
|
try expectVecApproxEqAbs(v, f32x4(1.0, 1.0, 0.0, 0.0), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn sqrt(v: anytype) @TypeOf(v) {
|
||
|
return @sqrt(v); // sqrtps
|
||
|
}
|
||
|
|
||
|
pub inline fn abs(v: anytype) @TypeOf(v) {
|
||
|
return @abs(v); // load, andps
|
||
|
}
|
||
|
|
||
|
pub inline fn select(mask: anytype, v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
return @select(f32, mask, v0, v1);
|
||
|
}
|
||
|
|
||
|
pub inline fn lerp(v0: anytype, v1: anytype, t: f32) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
return v0 + (v1 - v0) * splat(T, t); // subps, shufps, addps, mulps
|
||
|
}
|
||
|
|
||
|
pub inline fn lerpV(v0: anytype, v1: anytype, t: anytype) @TypeOf(v0, v1, t) {
|
||
|
return v0 + (v1 - v0) * t; // subps, addps, mulps
|
||
|
}
|
||
|
|
||
|
pub inline fn lerpInverse(v0: anytype, v1: anytype, t: anytype) @TypeOf(v0, v1) {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
return (splat(T, t) - v0) / (v1 - v0);
|
||
|
}
|
||
|
|
||
|
pub inline fn lerpInverseV(v0: anytype, v1: anytype, t: anytype) @TypeOf(v0, v1, t) {
|
||
|
return (t - v0) / (v1 - v0);
|
||
|
}
|
||
|
test "zmath.lerpInverse" {
|
||
|
try expect(math.approxEqAbs(f32, lerpInverseV(10.0, 100.0, 10.0), 0, 0.0005));
|
||
|
try expect(math.approxEqAbs(f32, lerpInverseV(10.0, 100.0, 100.0), 1, 0.0005));
|
||
|
try expect(math.approxEqAbs(f32, lerpInverseV(10.0, 100.0, 55.0), 0.5, 0.05));
|
||
|
try expectVecApproxEqAbs(lerpInverse(f32x4(0, 0, 10, 10), f32x4(100, 200, 100, 100), 10.0), f32x4(0.1, 0.05, 0, 0), 0.0005);
|
||
|
}
|
||
|
|
||
|
// Frame rate independent lerp (or "damp"), for approaching things over time.
|
||
|
// Reference: https://www.gamedeveloper.com/programming/improved-lerp-smoothing-
|
||
|
pub inline fn lerpOverTime(v0: anytype, v1: anytype, rate: anytype, dt: anytype) @TypeOf(v0, v1) {
|
||
|
const t = std.math.exp2(-rate * dt);
|
||
|
return lerp(v0, v1, t);
|
||
|
}
|
||
|
|
||
|
pub inline fn lerpVOverTime(v0: anytype, v1: anytype, rate: anytype, dt: anytype) @TypeOf(v0, v1, rate, dt) {
|
||
|
const t = std.math.exp2(-rate * dt);
|
||
|
return lerpV(v0, v1, t);
|
||
|
}
|
||
|
test "zmath.lerpOverTime" {
|
||
|
try expect(math.approxEqAbs(f32, lerpVOverTime(0.0, 1.0, 1.0, 1.0), 0.5, 0.0005));
|
||
|
try expect(math.approxEqAbs(f32, lerpVOverTime(0.5, 1.0, 1.0, 1.0), 0.75, 0.0005));
|
||
|
try expectVecApproxEqAbs(lerpOverTime(f32x4(0, 0, 10, 10), f32x4(100, 200, 100, 100), 1.0, 1.0), f32x4(50, 100, 55, 55), 0.0005);
|
||
|
}
|
||
|
|
||
|
/// To transform a vector of values from one range to another.
|
||
|
pub inline fn mapLinear(v: anytype, min1: anytype, max1: anytype, min2: anytype, max2: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
const min1V = splat(T, min1);
|
||
|
const max1V = splat(T, max1);
|
||
|
const min2V = splat(T, min2);
|
||
|
const max2V = splat(T, max2);
|
||
|
const dV = max1V - min1V;
|
||
|
return min2V + (v - min1V) * (max2V - min2V) / dV;
|
||
|
}
|
||
|
|
||
|
pub inline fn mapLinearV(v: anytype, min1: anytype, max1: anytype, min2: anytype, max2: anytype) @TypeOf(v, min1, max1, min2, max2) {
|
||
|
const d = max1 - min1;
|
||
|
return min2 + (v - min1) * (max2 - min2) / d;
|
||
|
}
|
||
|
test "zmath.mapLinear" {
|
||
|
try expect(math.approxEqAbs(f32, mapLinearV(0, 0, 1.2, 10, 100), 10, 0.0005));
|
||
|
try expect(math.approxEqAbs(f32, mapLinearV(1.2, 0, 1.2, 10, 100), 100, 0.0005));
|
||
|
try expect(math.approxEqAbs(f32, mapLinearV(0.6, 0, 1.2, 10, 100), 55, 0.0005));
|
||
|
try expectVecApproxEqAbs(mapLinearV(splat(F32x4, 0), splat(F32x4, 0), splat(F32x4, 1.2), splat(F32x4, 10), splat(F32x4, 100)), splat(F32x4, 10), 0.0005);
|
||
|
try expectVecApproxEqAbs(mapLinear(f32x4(0, 0, 0.6, 1.2), 0, 1.2, 10, 100), f32x4(10, 10, 55, 100), 0.0005);
|
||
|
}
|
||
|
|
||
|
pub const F32x4Component = enum { x, y, z, w };
|
||
|
|
||
|
pub inline fn swizzle(
|
||
|
v: F32x4,
|
||
|
comptime x: F32x4Component,
|
||
|
comptime y: F32x4Component,
|
||
|
comptime z: F32x4Component,
|
||
|
comptime w: F32x4Component,
|
||
|
) F32x4 {
|
||
|
return @shuffle(f32, v, undefined, [4]i32{ @intFromEnum(x), @intFromEnum(y), @intFromEnum(z), @intFromEnum(w) });
|
||
|
}
|
||
|
|
||
|
pub inline fn mod(v0: anytype, v1: anytype) @TypeOf(v0, v1) {
|
||
|
// vdivps, vroundps, vmulps, vsubps
|
||
|
return v0 - v1 * trunc(v0 / v1);
|
||
|
}
|
||
|
test "zmath.mod" {
|
||
|
try expectVecApproxEqAbs(mod(splat(F32x4, 3.1), splat(F32x4, 1.7)), splat(F32x4, 1.4), 0.0005);
|
||
|
try expectVecApproxEqAbs(mod(splat(F32x4, -3.0), splat(F32x4, 2.0)), splat(F32x4, -1.0), 0.0005);
|
||
|
try expectVecApproxEqAbs(mod(splat(F32x4, -3.0), splat(F32x4, -2.0)), splat(F32x4, -1.0), 0.0005);
|
||
|
try expectVecApproxEqAbs(mod(splat(F32x4, 3.0), splat(F32x4, -2.0)), splat(F32x4, 1.0), 0.0005);
|
||
|
try expect(all(isNan(mod(splat(F32x4, math.inf(f32)), splat(F32x4, 1.0))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, -math.inf(f32)), splat(F32x4, 123.456))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, math.nan(f32)), splat(F32x4, 123.456))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, math.snan(f32)), splat(F32x4, 123.456))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, -math.snan(f32)), splat(F32x4, 123.456))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, 123.456), splat(F32x4, math.inf(f32)))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, 123.456), splat(F32x4, -math.inf(f32)))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, math.inf(f32)), splat(F32x4, math.inf(f32)))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, 123.456), splat(F32x4, math.nan(f32)))), 0));
|
||
|
try expect(all(isNan(mod(splat(F32x4, math.inf(f32)), splat(F32x4, math.nan(f32)))), 0));
|
||
|
}
|
||
|
|
||
|
pub fn modAngle(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return switch (T) {
|
||
|
f32 => modAngle32(v),
|
||
|
F32x4, F32x8, F32x16 => modAngle32xN(v),
|
||
|
else => @compileError("zmath.modAngle() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub inline fn modAngle32xN(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return v - splat(T, math.tau) * round(v * splat(T, 1.0 / math.tau)); // 2 x vmulps, 2 x load, vroundps, vaddps
|
||
|
}
|
||
|
test "zmath.modAngle" {
|
||
|
try expectVecApproxEqAbs(modAngle(splat(F32x4, math.tau)), splat(F32x4, 0.0), 0.0005);
|
||
|
try expectVecApproxEqAbs(modAngle(splat(F32x4, 0.0)), splat(F32x4, 0.0), 0.0005);
|
||
|
try expectVecApproxEqAbs(modAngle(splat(F32x4, math.pi)), splat(F32x4, math.pi), 0.0005);
|
||
|
try expectVecApproxEqAbs(modAngle(splat(F32x4, 11 * math.pi)), splat(F32x4, math.pi), 0.0005);
|
||
|
try expectVecApproxEqAbs(modAngle(splat(F32x4, 3.5 * math.pi)), splat(F32x4, -0.5 * math.pi), 0.0005);
|
||
|
try expectVecApproxEqAbs(modAngle(splat(F32x4, 2.5 * math.pi)), splat(F32x4, 0.5 * math.pi), 0.0005);
|
||
|
}
|
||
|
|
||
|
pub inline fn mulAdd(v0: anytype, v1: anytype, v2: anytype) @TypeOf(v0, v1, v2) {
|
||
|
const T = @TypeOf(v0, v1, v2);
|
||
|
if (@import("zmath_options").enable_cross_platform_determinism) {
|
||
|
return v0 * v1 + v2; // Compiler will generate mul, add sequence (no fma even if the target supports it).
|
||
|
} else {
|
||
|
if (cpu_arch == .x86_64 and has_avx and has_fma) {
|
||
|
return @mulAdd(T, v0, v1, v2);
|
||
|
} else {
|
||
|
// NOTE(mziulek): On .x86_64 without HW fma instructions @mulAdd maps to really slow code!
|
||
|
return v0 * v1 + v2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn sin32xN(v: anytype) @TypeOf(v) {
|
||
|
// 11-degree minimax approximation
|
||
|
const T = @TypeOf(v);
|
||
|
|
||
|
var x = modAngle(v);
|
||
|
const sign = andInt(x, splatNegativeZero(T));
|
||
|
const c = orInt(sign, splat(T, math.pi));
|
||
|
const absx = andNotInt(sign, x);
|
||
|
const rflx = c - x;
|
||
|
const comp = absx <= splat(T, 0.5 * math.pi);
|
||
|
x = select(comp, x, rflx);
|
||
|
const x2 = x * x;
|
||
|
|
||
|
var result = mulAdd(splat(T, -2.3889859e-08), x2, splat(T, 2.7525562e-06));
|
||
|
result = mulAdd(result, x2, splat(T, -0.00019840874));
|
||
|
result = mulAdd(result, x2, splat(T, 0.0083333310));
|
||
|
result = mulAdd(result, x2, splat(T, -0.16666667));
|
||
|
result = mulAdd(result, x2, splat(T, 1.0));
|
||
|
return x * result;
|
||
|
}
|
||
|
test "zmath.sin" {
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
try expectVecApproxEqAbs(sin(splat(F32x4, 0.5 * math.pi)), splat(F32x4, 1.0), epsilon);
|
||
|
try expectVecApproxEqAbs(sin(splat(F32x4, 0.0)), splat(F32x4, 0.0), epsilon);
|
||
|
try expectVecApproxEqAbs(sin(splat(F32x4, -0.0)), splat(F32x4, -0.0), epsilon);
|
||
|
try expectVecApproxEqAbs(sin(splat(F32x4, 89.123)), splat(F32x4, 0.916166), epsilon);
|
||
|
try expectVecApproxEqAbs(sin(splat(F32x8, 89.123)), splat(F32x8, 0.916166), epsilon);
|
||
|
try expectVecApproxEqAbs(sin(splat(F32x16, 89.123)), splat(F32x16, 0.916166), epsilon);
|
||
|
try expect(all(isNan(sin(splat(F32x4, math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(sin(splat(F32x4, -math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(sin(splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(sin(splat(F32x4, math.snan(f32)))), 0) == true);
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const vr = sin(splat(F32x4, f));
|
||
|
const fr = @sin(splat(F32x4, f));
|
||
|
const vr8 = sin(splat(F32x8, f));
|
||
|
const fr8 = @sin(splat(F32x8, f));
|
||
|
const vr16 = sin(splat(F32x16, f));
|
||
|
const fr16 = @sin(splat(F32x16, f));
|
||
|
try expectVecApproxEqAbs(vr, fr, epsilon);
|
||
|
try expectVecApproxEqAbs(vr8, fr8, epsilon);
|
||
|
try expectVecApproxEqAbs(vr16, fr16, epsilon);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn cos32xN(v: anytype) @TypeOf(v) {
|
||
|
// 10-degree minimax approximation
|
||
|
const T = @TypeOf(v);
|
||
|
|
||
|
var x = modAngle(v);
|
||
|
var sign = andInt(x, splatNegativeZero(T));
|
||
|
const c = orInt(sign, splat(T, math.pi));
|
||
|
const absx = andNotInt(sign, x);
|
||
|
const rflx = c - x;
|
||
|
const comp = absx <= splat(T, 0.5 * math.pi);
|
||
|
x = select(comp, x, rflx);
|
||
|
sign = select(comp, splat(T, 1.0), splat(T, -1.0));
|
||
|
const x2 = x * x;
|
||
|
|
||
|
var result = mulAdd(splat(T, -2.6051615e-07), x2, splat(T, 2.4760495e-05));
|
||
|
result = mulAdd(result, x2, splat(T, -0.0013888378));
|
||
|
result = mulAdd(result, x2, splat(T, 0.041666638));
|
||
|
result = mulAdd(result, x2, splat(T, -0.5));
|
||
|
result = mulAdd(result, x2, splat(T, 1.0));
|
||
|
return sign * result;
|
||
|
}
|
||
|
test "zmath.cos" {
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
try expectVecApproxEqAbs(cos(splat(F32x4, 0.5 * math.pi)), splat(F32x4, 0.0), epsilon);
|
||
|
try expectVecApproxEqAbs(cos(splat(F32x4, 0.0)), splat(F32x4, 1.0), epsilon);
|
||
|
try expectVecApproxEqAbs(cos(splat(F32x4, -0.0)), splat(F32x4, 1.0), epsilon);
|
||
|
try expect(all(isNan(cos(splat(F32x4, math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(cos(splat(F32x4, -math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(cos(splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(cos(splat(F32x4, math.snan(f32)))), 0) == true);
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const vr = cos(splat(F32x4, f));
|
||
|
const fr = @cos(splat(F32x4, f));
|
||
|
const vr8 = cos(splat(F32x8, f));
|
||
|
const fr8 = @cos(splat(F32x8, f));
|
||
|
const vr16 = cos(splat(F32x16, f));
|
||
|
const fr16 = @cos(splat(F32x16, f));
|
||
|
try expectVecApproxEqAbs(vr, fr, epsilon);
|
||
|
try expectVecApproxEqAbs(vr8, fr8, epsilon);
|
||
|
try expectVecApproxEqAbs(vr16, fr16, epsilon);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn sin(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return switch (T) {
|
||
|
f32 => sin32(v),
|
||
|
F32x4, F32x8, F32x16 => sin32xN(v),
|
||
|
else => @compileError("zmath.sin() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn cos(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return switch (T) {
|
||
|
f32 => cos32(v),
|
||
|
F32x4, F32x8, F32x16 => cos32xN(v),
|
||
|
else => @compileError("zmath.cos() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn sincos(v: anytype) [2]@TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return switch (T) {
|
||
|
f32 => sincos32(v),
|
||
|
F32x4, F32x8, F32x16 => sincos32xN(v),
|
||
|
else => @compileError("zmath.sincos() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn asin(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return switch (T) {
|
||
|
f32 => asin32(v),
|
||
|
F32x4, F32x8, F32x16 => asin32xN(v),
|
||
|
else => @compileError("zmath.asin() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn acos(v: anytype) @TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
return switch (T) {
|
||
|
f32 => acos32(v),
|
||
|
F32x4, F32x8, F32x16 => acos32xN(v),
|
||
|
else => @compileError("zmath.acos() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn sincos32xN(v: anytype) [2]@TypeOf(v) {
|
||
|
const T = @TypeOf(v);
|
||
|
|
||
|
var x = modAngle(v);
|
||
|
var sign = andInt(x, splatNegativeZero(T));
|
||
|
const c = orInt(sign, splat(T, math.pi));
|
||
|
const absx = andNotInt(sign, x);
|
||
|
const rflx = c - x;
|
||
|
const comp = absx <= splat(T, 0.5 * math.pi);
|
||
|
x = select(comp, x, rflx);
|
||
|
sign = select(comp, splat(T, 1.0), splat(T, -1.0));
|
||
|
const x2 = x * x;
|
||
|
|
||
|
var sresult = mulAdd(splat(T, -2.3889859e-08), x2, splat(T, 2.7525562e-06));
|
||
|
sresult = mulAdd(sresult, x2, splat(T, -0.00019840874));
|
||
|
sresult = mulAdd(sresult, x2, splat(T, 0.0083333310));
|
||
|
sresult = mulAdd(sresult, x2, splat(T, -0.16666667));
|
||
|
sresult = x * mulAdd(sresult, x2, splat(T, 1.0));
|
||
|
|
||
|
var cresult = mulAdd(splat(T, -2.6051615e-07), x2, splat(T, 2.4760495e-05));
|
||
|
cresult = mulAdd(cresult, x2, splat(T, -0.0013888378));
|
||
|
cresult = mulAdd(cresult, x2, splat(T, 0.041666638));
|
||
|
cresult = mulAdd(cresult, x2, splat(T, -0.5));
|
||
|
cresult = sign * mulAdd(cresult, x2, splat(T, 1.0));
|
||
|
|
||
|
return .{ sresult, cresult };
|
||
|
}
|
||
|
test "zmath.sincos32xN" {
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const sc = sincos(splat(F32x4, f));
|
||
|
const sc8 = sincos(splat(F32x8, f));
|
||
|
const sc16 = sincos(splat(F32x16, f));
|
||
|
const s4 = @sin(splat(F32x4, f));
|
||
|
const s8 = @sin(splat(F32x8, f));
|
||
|
const s16 = @sin(splat(F32x16, f));
|
||
|
const c4 = @cos(splat(F32x4, f));
|
||
|
const c8 = @cos(splat(F32x8, f));
|
||
|
const c16 = @cos(splat(F32x16, f));
|
||
|
try expectVecApproxEqAbs(sc[0], s4, epsilon);
|
||
|
try expectVecApproxEqAbs(sc8[0], s8, epsilon);
|
||
|
try expectVecApproxEqAbs(sc16[0], s16, epsilon);
|
||
|
try expectVecApproxEqAbs(sc[1], c4, epsilon);
|
||
|
try expectVecApproxEqAbs(sc8[1], c8, epsilon);
|
||
|
try expectVecApproxEqAbs(sc16[1], c16, epsilon);
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn asin32xN(v: anytype) @TypeOf(v) {
|
||
|
// 7-degree minimax approximation
|
||
|
const T = @TypeOf(v);
|
||
|
|
||
|
const x = abs(v);
|
||
|
const root = sqrt(maxFast(splat(T, 0.0), splat(T, 1.0) - x));
|
||
|
|
||
|
var t0 = mulAdd(splat(T, -0.0012624911), x, splat(T, 0.0066700901));
|
||
|
t0 = mulAdd(t0, x, splat(T, -0.0170881256));
|
||
|
t0 = mulAdd(t0, x, splat(T, 0.0308918810));
|
||
|
t0 = mulAdd(t0, x, splat(T, -0.0501743046));
|
||
|
t0 = mulAdd(t0, x, splat(T, 0.0889789874));
|
||
|
t0 = mulAdd(t0, x, splat(T, -0.2145988016));
|
||
|
t0 = root * mulAdd(t0, x, splat(T, 1.5707963050));
|
||
|
|
||
|
const t1 = splat(T, math.pi) - t0;
|
||
|
return splat(T, 0.5 * math.pi) - select(v >= splat(T, 0.0), t0, t1);
|
||
|
}
|
||
|
|
||
|
fn acos32xN(v: anytype) @TypeOf(v) {
|
||
|
// 7-degree minimax approximation
|
||
|
const T = @TypeOf(v);
|
||
|
|
||
|
const x = abs(v);
|
||
|
const root = sqrt(maxFast(splat(T, 0.0), splat(T, 1.0) - x));
|
||
|
|
||
|
var t0 = mulAdd(splat(T, -0.0012624911), x, splat(T, 0.0066700901));
|
||
|
t0 = mulAdd(t0, x, splat(T, -0.0170881256));
|
||
|
t0 = mulAdd(t0, x, splat(T, 0.0308918810));
|
||
|
t0 = mulAdd(t0, x, splat(T, -0.0501743046));
|
||
|
t0 = mulAdd(t0, x, splat(T, 0.0889789874));
|
||
|
t0 = mulAdd(t0, x, splat(T, -0.2145988016));
|
||
|
t0 = root * mulAdd(t0, x, splat(T, 1.5707963050));
|
||
|
|
||
|
const t1 = splat(T, math.pi) - t0;
|
||
|
return select(v >= splat(T, 0.0), t0, t1);
|
||
|
}
|
||
|
|
||
|
pub fn atan(v: anytype) @TypeOf(v) {
|
||
|
// 17-degree minimax approximation
|
||
|
const T = @TypeOf(v);
|
||
|
|
||
|
const vabs = abs(v);
|
||
|
const vinv = splat(T, 1.0) / v;
|
||
|
var sign = select(v > splat(T, 1.0), splat(T, 1.0), splat(T, -1.0));
|
||
|
const comp = vabs <= splat(T, 1.0);
|
||
|
sign = select(comp, splat(T, 0.0), sign);
|
||
|
const x = select(comp, v, vinv);
|
||
|
const x2 = x * x;
|
||
|
|
||
|
var result = mulAdd(splat(T, 0.0028662257), x2, splat(T, -0.0161657367));
|
||
|
result = mulAdd(result, x2, splat(T, 0.0429096138));
|
||
|
result = mulAdd(result, x2, splat(T, -0.0752896400));
|
||
|
result = mulAdd(result, x2, splat(T, 0.1065626393));
|
||
|
result = mulAdd(result, x2, splat(T, -0.1420889944));
|
||
|
result = mulAdd(result, x2, splat(T, 0.1999355085));
|
||
|
result = mulAdd(result, x2, splat(T, -0.3333314528));
|
||
|
result = x * mulAdd(result, x2, splat(T, 1.0));
|
||
|
|
||
|
const result1 = sign * splat(T, 0.5 * math.pi) - result;
|
||
|
return select(sign == splat(T, 0.0), result, result1);
|
||
|
}
|
||
|
test "zmath.atan" {
|
||
|
const epsilon = 0.0001;
|
||
|
{
|
||
|
const v = f32x4(0.25, 0.5, 1.0, 1.25);
|
||
|
const e = f32x4(math.atan(v[0]), math.atan(v[1]), math.atan(v[2]), math.atan(v[3]));
|
||
|
try expectVecApproxEqAbs(e, atan(v), epsilon);
|
||
|
}
|
||
|
{
|
||
|
const v = f32x8(-0.25, 0.5, -1.0, 1.25, 100.0, -200.0, 300.0, 400.0);
|
||
|
// zig fmt: off
|
||
|
const e = f32x8(
|
||
|
math.atan(v[0]), math.atan(v[1]), math.atan(v[2]), math.atan(v[3]),
|
||
|
math.atan(v[4]), math.atan(v[5]), math.atan(v[6]), math.atan(v[7]),
|
||
|
);
|
||
|
// zig fmt: on
|
||
|
try expectVecApproxEqAbs(e, atan(v), epsilon);
|
||
|
}
|
||
|
{
|
||
|
// zig fmt: off
|
||
|
const v = f32x16(
|
||
|
-0.25, 0.5, -1.0, 0.0, 0.1, -0.2, 30.0, 400.0,
|
||
|
-0.25, 0.5, -1.0, -0.0, -0.05, -0.125, 0.0625, 4000.0
|
||
|
);
|
||
|
const e = f32x16(
|
||
|
math.atan(v[0]), math.atan(v[1]), math.atan(v[2]), math.atan(v[3]),
|
||
|
math.atan(v[4]), math.atan(v[5]), math.atan(v[6]), math.atan(v[7]),
|
||
|
math.atan(v[8]), math.atan(v[9]), math.atan(v[10]), math.atan(v[11]),
|
||
|
math.atan(v[12]), math.atan(v[13]), math.atan(v[14]), math.atan(v[15]),
|
||
|
);
|
||
|
// zig fmt: on
|
||
|
try expectVecApproxEqAbs(e, atan(v), epsilon);
|
||
|
}
|
||
|
{
|
||
|
try expectVecApproxEqAbs(atan(splat(F32x4, math.inf(f32))), splat(F32x4, 0.5 * math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(atan(splat(F32x4, -math.inf(f32))), splat(F32x4, -0.5 * math.pi), epsilon);
|
||
|
try expect(all(isNan(atan(splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(atan(splat(F32x4, -math.nan(f32)))), 0) == true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn atan2(vy: anytype, vx: anytype) @TypeOf(vx, vy) {
|
||
|
const T = @TypeOf(vx, vy);
|
||
|
const Tu = @Vector(veclen(T), u32);
|
||
|
|
||
|
const vx_is_positive =
|
||
|
(@as(Tu, @bitCast(vx)) & @as(Tu, @splat(0x8000_0000))) == @as(Tu, @splat(0));
|
||
|
|
||
|
const vy_sign = andInt(vy, splatNegativeZero(T));
|
||
|
const c0_25pi = orInt(vy_sign, @as(T, @splat(0.25 * math.pi)));
|
||
|
const c0_50pi = orInt(vy_sign, @as(T, @splat(0.50 * math.pi)));
|
||
|
const c0_75pi = orInt(vy_sign, @as(T, @splat(0.75 * math.pi)));
|
||
|
const c1_00pi = orInt(vy_sign, @as(T, @splat(1.00 * math.pi)));
|
||
|
|
||
|
var r1 = select(vx_is_positive, vy_sign, c1_00pi);
|
||
|
var r2 = select(vx == splat(T, 0.0), c0_50pi, splatInt(T, 0xffff_ffff));
|
||
|
const r3 = select(vy == splat(T, 0.0), r1, r2);
|
||
|
const r4 = select(vx_is_positive, c0_25pi, c0_75pi);
|
||
|
const r5 = select(isInf(vx), r4, c0_50pi);
|
||
|
const result = select(isInf(vy), r5, r3);
|
||
|
const result_valid = @as(Tu, @bitCast(result)) == @as(Tu, @splat(0xffff_ffff));
|
||
|
|
||
|
const v = vy / vx;
|
||
|
const r0 = atan(v);
|
||
|
|
||
|
r1 = select(vx_is_positive, splatNegativeZero(T), c1_00pi);
|
||
|
r2 = r0 + r1;
|
||
|
|
||
|
return select(result_valid, r2, result);
|
||
|
}
|
||
|
test "zmath.atan2" {
|
||
|
// From DirectXMath XMVectorATan2():
|
||
|
//
|
||
|
// Return the inverse tangent of Y / X in the range of -Pi to Pi with the following exceptions:
|
||
|
|
||
|
// Y == 0 and X is Negative -> Pi with the sign of Y
|
||
|
// y == 0 and x is positive -> 0 with the sign of y
|
||
|
// Y != 0 and X == 0 -> Pi / 2 with the sign of Y
|
||
|
// Y != 0 and X is Negative -> atan(y/x) + (PI with the sign of Y)
|
||
|
// X == -Infinity and Finite Y -> Pi with the sign of Y
|
||
|
// X == +Infinity and Finite Y -> 0 with the sign of Y
|
||
|
// Y == Infinity and X is Finite -> Pi / 2 with the sign of Y
|
||
|
// Y == Infinity and X == -Infinity -> 3Pi / 4 with the sign of Y
|
||
|
// Y == Infinity and X == +Infinity -> Pi / 4 with the sign of Y
|
||
|
|
||
|
const epsilon = 0.0001;
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, 0.0), splat(F32x4, -1.0)), splat(F32x4, math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, -0.0), splat(F32x4, -1.0)), splat(F32x4, -math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, 1.0), splat(F32x4, 0.0)), splat(F32x4, 0.5 * math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, -1.0), splat(F32x4, 0.0)), splat(F32x4, -0.5 * math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, 1.0), splat(F32x4, -1.0)),
|
||
|
splat(F32x4, math.atan(@as(f32, -1.0)) + math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, -10.0), splat(F32x4, -2.0)),
|
||
|
splat(F32x4, math.atan(@as(f32, 5.0)) - math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, 1.0), splat(F32x4, -math.inf(f32))), splat(F32x4, math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, -1.0), splat(F32x4, -math.inf(f32))), splat(F32x4, -math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, 1.0), splat(F32x4, math.inf(f32))), splat(F32x4, 0.0), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, -1.0), splat(F32x4, math.inf(f32))), splat(F32x4, -0.0), epsilon);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, math.inf(f32)), splat(F32x4, 2.0)),
|
||
|
splat(F32x4, 0.5 * math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, -math.inf(f32)), splat(F32x4, 2.0)),
|
||
|
splat(F32x4, -0.5 * math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, math.inf(f32)), splat(F32x4, -math.inf(f32))),
|
||
|
splat(F32x4, 0.75 * math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, -math.inf(f32)), splat(F32x4, -math.inf(f32))),
|
||
|
splat(F32x4, -0.75 * math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, math.inf(f32)), splat(F32x4, math.inf(f32))),
|
||
|
splat(F32x4, 0.25 * math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(splat(F32x4, -math.inf(f32)), splat(F32x4, math.inf(f32))),
|
||
|
splat(F32x4, -0.25 * math.pi),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
atan2(
|
||
|
f32x8(0.0, -math.inf(f32), -0.0, 2.0, math.inf(f32), math.inf(f32), 1.0, -math.inf(f32)),
|
||
|
f32x8(-2.0, math.inf(f32), 1.0, 0.0, 10.0, -math.inf(f32), 1.0, -math.inf(f32)),
|
||
|
),
|
||
|
f32x8(
|
||
|
math.pi,
|
||
|
-0.25 * math.pi,
|
||
|
-0.0,
|
||
|
0.5 * math.pi,
|
||
|
0.5 * math.pi,
|
||
|
0.75 * math.pi,
|
||
|
math.atan(@as(f32, 1.0)),
|
||
|
-0.75 * math.pi,
|
||
|
),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, 0.0), splat(F32x4, 0.0)), splat(F32x4, 0.0), epsilon);
|
||
|
try expectVecApproxEqAbs(atan2(splat(F32x4, -0.0), splat(F32x4, 0.0)), splat(F32x4, 0.0), epsilon);
|
||
|
try expect(all(isNan(atan2(splat(F32x4, 1.0), splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(atan2(splat(F32x4, -1.0), splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(atan2(splat(F32x4, math.nan(f32)), splat(F32x4, -1.0))), 0) == true);
|
||
|
try expect(all(isNan(atan2(splat(F32x4, -math.nan(f32)), splat(F32x4, 1.0))), 0) == true);
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 3. 2D, 3D, 4D vector functions
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub inline fn dot2(v0: Vec, v1: Vec) F32x4 {
|
||
|
var xmm0 = v0 * v1; // | x0*x1 | y0*y1 | -- | -- |
|
||
|
const xmm1 = swizzle(xmm0, .y, .x, .x, .x); // | y0*y1 | -- | -- | -- |
|
||
|
xmm0 = f32x4(xmm0[0] + xmm1[0], xmm0[1], xmm0[2], xmm0[3]); // | x0*x1 + y0*y1 | -- | -- | -- |
|
||
|
return swizzle(xmm0, .x, .x, .x, .x);
|
||
|
}
|
||
|
test "zmath.dot2" {
|
||
|
const v0 = f32x4(-1.0, 2.0, 300.0, -2.0);
|
||
|
const v1 = f32x4(4.0, 5.0, 600.0, 2.0);
|
||
|
const v = dot2(v0, v1);
|
||
|
try expectVecApproxEqAbs(v, splat(F32x4, 6.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub inline fn dot3(v0: Vec, v1: Vec) F32x4 {
|
||
|
const dot = v0 * v1;
|
||
|
return f32x4s(dot[0] + dot[1] + dot[2]);
|
||
|
}
|
||
|
test "zmath.dot3" {
|
||
|
const v0 = f32x4(-1.0, 2.0, 3.0, 1.0);
|
||
|
const v1 = f32x4(4.0, 5.0, 6.0, 1.0);
|
||
|
const v = dot3(v0, v1);
|
||
|
try expectVecApproxEqAbs(v, splat(F32x4, 24.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub inline fn dot4(v0: Vec, v1: Vec) F32x4 {
|
||
|
var xmm0 = v0 * v1; // | x0*x1 | y0*y1 | z0*z1 | w0*w1 |
|
||
|
var xmm1 = swizzle(xmm0, .y, .x, .w, .x); // | y0*y1 | -- | w0*w1 | -- |
|
||
|
xmm1 = xmm0 + xmm1; // | x0*x1 + y0*y1 | -- | z0*z1 + w0*w1 | -- |
|
||
|
xmm0 = swizzle(xmm1, .z, .x, .x, .x); // | z0*z1 + w0*w1 | -- | -- | -- |
|
||
|
xmm0 = f32x4(xmm0[0] + xmm1[0], xmm0[1], xmm0[2], xmm0[2]); // addss
|
||
|
return swizzle(xmm0, .x, .x, .x, .x);
|
||
|
}
|
||
|
test "zmath.dot4" {
|
||
|
const v0 = f32x4(-1.0, 2.0, 3.0, -2.0);
|
||
|
const v1 = f32x4(4.0, 5.0, 6.0, 2.0);
|
||
|
const v = dot4(v0, v1);
|
||
|
try expectVecApproxEqAbs(v, splat(F32x4, 20.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub inline fn cross3(v0: Vec, v1: Vec) Vec {
|
||
|
var xmm0 = swizzle(v0, .y, .z, .x, .w);
|
||
|
var xmm1 = swizzle(v1, .z, .x, .y, .w);
|
||
|
var result = xmm0 * xmm1;
|
||
|
xmm0 = swizzle(xmm0, .y, .z, .x, .w);
|
||
|
xmm1 = swizzle(xmm1, .z, .x, .y, .w);
|
||
|
result = result - xmm0 * xmm1;
|
||
|
return andInt(result, f32x4_mask3);
|
||
|
}
|
||
|
test "zmath.cross3" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 0.0, 0.0, 1.0);
|
||
|
const v1 = f32x4(0.0, 1.0, 0.0, 1.0);
|
||
|
const v = cross3(v0, v1);
|
||
|
try expectVecApproxEqAbs(v, f32x4(0.0, 0.0, 1.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(1.0, 0.0, 0.0, 1.0);
|
||
|
const v1 = f32x4(0.0, -1.0, 0.0, 1.0);
|
||
|
const v = cross3(v0, v1);
|
||
|
try expectVecApproxEqAbs(v, f32x4(0.0, 0.0, -1.0, 0.0), 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const v0 = f32x4(-3.0, 0, -2.0, 1.0);
|
||
|
const v1 = f32x4(5.0, -1.0, 2.0, 1.0);
|
||
|
const v = cross3(v0, v1);
|
||
|
try expectVecApproxEqAbs(v, f32x4(-2.0, -4.0, 3.0, 0.0), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn lengthSq2(v: Vec) F32x4 {
|
||
|
return dot2(v, v);
|
||
|
}
|
||
|
pub inline fn lengthSq3(v: Vec) F32x4 {
|
||
|
return dot3(v, v);
|
||
|
}
|
||
|
pub inline fn lengthSq4(v: Vec) F32x4 {
|
||
|
return dot4(v, v);
|
||
|
}
|
||
|
|
||
|
pub inline fn length2(v: Vec) F32x4 {
|
||
|
return sqrt(dot2(v, v));
|
||
|
}
|
||
|
pub inline fn length3(v: Vec) F32x4 {
|
||
|
return sqrt(dot3(v, v));
|
||
|
}
|
||
|
pub inline fn length4(v: Vec) F32x4 {
|
||
|
return sqrt(dot4(v, v));
|
||
|
}
|
||
|
test "zmath.length3" {
|
||
|
{
|
||
|
const v = length3(f32x4(1.0, -2.0, 3.0, 1000.0));
|
||
|
try expectVecApproxEqAbs(v, splat(F32x4, math.sqrt(14.0)), 0.001);
|
||
|
}
|
||
|
{
|
||
|
const v = length3(f32x4(1.0, math.nan(f32), math.nan(f32), 1000.0));
|
||
|
try expect(all(isNan(v), 0));
|
||
|
}
|
||
|
{
|
||
|
const v = length3(f32x4(1.0, math.inf(f32), 3.0, 1000.0));
|
||
|
try expect(all(isInf(v), 0));
|
||
|
}
|
||
|
{
|
||
|
const v = length3(f32x4(3.0, 2.0, 1.0, math.nan(f32)));
|
||
|
try expectVecApproxEqAbs(v, splat(F32x4, math.sqrt(14.0)), 0.001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn normalize2(v: Vec) Vec {
|
||
|
return v * splat(F32x4, 1.0) / sqrt(dot2(v, v));
|
||
|
}
|
||
|
pub inline fn normalize3(v: Vec) Vec {
|
||
|
return v * splat(F32x4, 1.0) / sqrt(dot3(v, v));
|
||
|
}
|
||
|
pub inline fn normalize4(v: Vec) Vec {
|
||
|
return v * splat(F32x4, 1.0) / sqrt(dot4(v, v));
|
||
|
}
|
||
|
test "zmath.normalize3" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, -2.0, 3.0, 1000.0);
|
||
|
const v = normalize3(v0);
|
||
|
try expectVecApproxEqAbs(v, v0 * splat(F32x4, 1.0 / math.sqrt(14.0)), 0.0005);
|
||
|
}
|
||
|
{
|
||
|
try expect(any(isNan(normalize3(f32x4(1.0, math.inf(f32), 1.0, 1.0))), 0));
|
||
|
try expect(any(isNan(normalize3(f32x4(-math.inf(f32), math.inf(f32), 0.0, 0.0))), 0));
|
||
|
try expect(any(isNan(normalize3(f32x4(-math.nan(f32), math.snan(f32), 0.0, 0.0))), 0));
|
||
|
try expect(any(isNan(normalize3(f32x4(0, 0, 0, 0))), 0));
|
||
|
}
|
||
|
}
|
||
|
test "zmath.normalize4" {
|
||
|
{
|
||
|
const v0 = f32x4(1.0, -2.0, 3.0, 10.0);
|
||
|
const v = normalize4(v0);
|
||
|
try expectVecApproxEqAbs(v, v0 * splat(F32x4, 1.0 / math.sqrt(114.0)), 0.0005);
|
||
|
}
|
||
|
{
|
||
|
try expect(any(isNan(normalize4(f32x4(1.0, math.inf(f32), 1.0, 1.0))), 0));
|
||
|
try expect(any(isNan(normalize4(f32x4(-math.inf(f32), math.inf(f32), 0.0, 0.0))), 0));
|
||
|
try expect(any(isNan(normalize4(f32x4(-math.nan(f32), math.snan(f32), 0.0, 0.0))), 0));
|
||
|
try expect(any(isNan(normalize4(f32x4(0, 0, 0, 0))), 0));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn vecMulMat(v: Vec, m: Mat) Vec {
|
||
|
const vx = @shuffle(f32, v, undefined, [4]i32{ 0, 0, 0, 0 });
|
||
|
const vy = @shuffle(f32, v, undefined, [4]i32{ 1, 1, 1, 1 });
|
||
|
const vz = @shuffle(f32, v, undefined, [4]i32{ 2, 2, 2, 2 });
|
||
|
const vw = @shuffle(f32, v, undefined, [4]i32{ 3, 3, 3, 3 });
|
||
|
return vx * m[0] + vy * m[1] + vz * m[2] + vw * m[3];
|
||
|
}
|
||
|
fn matMulVec(m: Mat, v: Vec) Vec {
|
||
|
return .{ dot4(m[0], v)[0], dot4(m[1], v)[0], dot4(m[2], v)[0], dot4(m[3], v)[0] };
|
||
|
}
|
||
|
test "zmath.vecMulMat" {
|
||
|
const m = Mat{
|
||
|
f32x4(1.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 1.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 1.0, 0.0),
|
||
|
f32x4(2.0, 3.0, 4.0, 1.0),
|
||
|
};
|
||
|
const vm = mul(f32x4(1.0, 2.0, 3.0, 1.0), m);
|
||
|
const mv = mul(m, f32x4(1.0, 2.0, 3.0, 1.0));
|
||
|
const v = mul(transpose(m), f32x4(1.0, 2.0, 3.0, 1.0));
|
||
|
try expectVecApproxEqAbs(vm, f32x4(3.0, 5.0, 7.0, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(mv, f32x4(1.0, 2.0, 3.0, 21.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(v, f32x4(3.0, 5.0, 7.0, 1.0), 0.0001);
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 4. Matrix functions
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub fn identity() Mat {
|
||
|
const static = struct {
|
||
|
const identity = Mat{
|
||
|
f32x4(1.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 1.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 1.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 1.0),
|
||
|
};
|
||
|
};
|
||
|
return static.identity;
|
||
|
}
|
||
|
|
||
|
pub fn matFromArr(arr: [16]f32) Mat {
|
||
|
return Mat{
|
||
|
f32x4(arr[0], arr[1], arr[2], arr[3]),
|
||
|
f32x4(arr[4], arr[5], arr[6], arr[7]),
|
||
|
f32x4(arr[8], arr[9], arr[10], arr[11]),
|
||
|
f32x4(arr[12], arr[13], arr[14], arr[15]),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn mulRetType(comptime Ta: type, comptime Tb: type) type {
|
||
|
if (Ta == Mat and Tb == Mat) {
|
||
|
return Mat;
|
||
|
} else if ((Ta == f32 and Tb == Mat) or (Ta == Mat and Tb == f32)) {
|
||
|
return Mat;
|
||
|
} else if ((Ta == Vec and Tb == Mat) or (Ta == Mat and Tb == Vec)) {
|
||
|
return Vec;
|
||
|
}
|
||
|
@compileError("zmath.mul() not implemented for types: " ++ @typeName(Ta) ++ @typeName(Tb));
|
||
|
}
|
||
|
|
||
|
pub fn mul(a: anytype, b: anytype) mulRetType(@TypeOf(a), @TypeOf(b)) {
|
||
|
const Ta = @TypeOf(a);
|
||
|
const Tb = @TypeOf(b);
|
||
|
if (Ta == Mat and Tb == Mat) {
|
||
|
return mulMat(a, b);
|
||
|
} else if (Ta == f32 and Tb == Mat) {
|
||
|
const va = splat(F32x4, a);
|
||
|
return Mat{ va * b[0], va * b[1], va * b[2], va * b[3] };
|
||
|
} else if (Ta == Mat and Tb == f32) {
|
||
|
const vb = splat(F32x4, b);
|
||
|
return Mat{ a[0] * vb, a[1] * vb, a[2] * vb, a[3] * vb };
|
||
|
} else if (Ta == Vec and Tb == Mat) {
|
||
|
return vecMulMat(a, b);
|
||
|
} else if (Ta == Mat and Tb == Vec) {
|
||
|
return matMulVec(a, b);
|
||
|
} else {
|
||
|
@compileError("zmath.mul() not implemented for types: " ++ @typeName(Ta) ++ ", " ++ @typeName(Tb));
|
||
|
}
|
||
|
}
|
||
|
test "zmath.mul" {
|
||
|
{
|
||
|
const m = Mat{
|
||
|
f32x4(0.1, 0.2, 0.3, 0.4),
|
||
|
f32x4(0.5, 0.6, 0.7, 0.8),
|
||
|
f32x4(0.9, 1.0, 1.1, 1.2),
|
||
|
f32x4(1.3, 1.4, 1.5, 1.6),
|
||
|
};
|
||
|
const ms = mul(@as(f32, 2.0), m);
|
||
|
try expectVecApproxEqAbs(ms[0], f32x4(0.2, 0.4, 0.6, 0.8), 0.0001);
|
||
|
try expectVecApproxEqAbs(ms[1], f32x4(1.0, 1.2, 1.4, 1.6), 0.0001);
|
||
|
try expectVecApproxEqAbs(ms[2], f32x4(1.8, 2.0, 2.2, 2.4), 0.0001);
|
||
|
try expectVecApproxEqAbs(ms[3], f32x4(2.6, 2.8, 3.0, 3.2), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn mulMat(m0: Mat, m1: Mat) Mat {
|
||
|
var result: Mat = undefined;
|
||
|
comptime var row: u32 = 0;
|
||
|
inline while (row < 4) : (row += 1) {
|
||
|
const vx = swizzle(m0[row], .x, .x, .x, .x);
|
||
|
const vy = swizzle(m0[row], .y, .y, .y, .y);
|
||
|
const vz = swizzle(m0[row], .z, .z, .z, .z);
|
||
|
const vw = swizzle(m0[row], .w, .w, .w, .w);
|
||
|
result[row] = mulAdd(vx, m1[0], vz * m1[2]) + mulAdd(vy, m1[1], vw * m1[3]);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
test "zmath.matrix.mul" {
|
||
|
const a = Mat{
|
||
|
f32x4(0.1, 0.2, 0.3, 0.4),
|
||
|
f32x4(0.5, 0.6, 0.7, 0.8),
|
||
|
f32x4(0.9, 1.0, 1.1, 1.2),
|
||
|
f32x4(1.3, 1.4, 1.5, 1.6),
|
||
|
};
|
||
|
const b = Mat{
|
||
|
f32x4(1.7, 1.8, 1.9, 2.0),
|
||
|
f32x4(2.1, 2.2, 2.3, 2.4),
|
||
|
f32x4(2.5, 2.6, 2.7, 2.8),
|
||
|
f32x4(2.9, 3.0, 3.1, 3.2),
|
||
|
};
|
||
|
const c = mul(a, b);
|
||
|
try expectVecApproxEqAbs(c[0], f32x4(2.5, 2.6, 2.7, 2.8), 0.0001);
|
||
|
try expectVecApproxEqAbs(c[1], f32x4(6.18, 6.44, 6.7, 6.96), 0.0001);
|
||
|
try expectVecApproxEqAbs(c[2], f32x4(9.86, 10.28, 10.7, 11.12), 0.0001);
|
||
|
try expectVecApproxEqAbs(c[3], f32x4(13.54, 14.12, 14.7, 15.28), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub fn transpose(m: Mat) Mat {
|
||
|
const temp1 = @shuffle(f32, m[0], m[1], [4]i32{ 0, 1, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
const temp3 = @shuffle(f32, m[0], m[1], [4]i32{ 2, 3, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
const temp2 = @shuffle(f32, m[2], m[3], [4]i32{ 0, 1, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
const temp4 = @shuffle(f32, m[2], m[3], [4]i32{ 2, 3, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
return .{
|
||
|
@shuffle(f32, temp1, temp2, [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) }),
|
||
|
@shuffle(f32, temp1, temp2, [4]i32{ 1, 3, ~@as(i32, 1), ~@as(i32, 3) }),
|
||
|
@shuffle(f32, temp3, temp4, [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) }),
|
||
|
@shuffle(f32, temp3, temp4, [4]i32{ 1, 3, ~@as(i32, 1), ~@as(i32, 3) }),
|
||
|
};
|
||
|
}
|
||
|
test "zmath.matrix.transpose" {
|
||
|
const m = Mat{
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0),
|
||
|
f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0),
|
||
|
f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
};
|
||
|
const mt = transpose(m);
|
||
|
try expectVecApproxEqAbs(mt[0], f32x4(1.0, 5.0, 9.0, 13.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(mt[1], f32x4(2.0, 6.0, 10.0, 14.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(mt[2], f32x4(3.0, 7.0, 11.0, 15.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(mt[3], f32x4(4.0, 8.0, 12.0, 16.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub fn rotationX(angle: f32) Mat {
|
||
|
const sc = sincos(angle);
|
||
|
return .{
|
||
|
f32x4(1.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, sc[1], sc[0], 0.0),
|
||
|
f32x4(0.0, -sc[0], sc[1], 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn rotationY(angle: f32) Mat {
|
||
|
const sc = sincos(angle);
|
||
|
return .{
|
||
|
f32x4(sc[1], 0.0, -sc[0], 0.0),
|
||
|
f32x4(0.0, 1.0, 0.0, 0.0),
|
||
|
f32x4(sc[0], 0.0, sc[1], 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn rotationZ(angle: f32) Mat {
|
||
|
const sc = sincos(angle);
|
||
|
return .{
|
||
|
f32x4(sc[1], sc[0], 0.0, 0.0),
|
||
|
f32x4(-sc[0], sc[1], 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 1.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn translation(x: f32, y: f32, z: f32) Mat {
|
||
|
return .{
|
||
|
f32x4(1.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 1.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 1.0, 0.0),
|
||
|
f32x4(x, y, z, 1.0),
|
||
|
};
|
||
|
}
|
||
|
pub fn translationV(v: Vec) Mat {
|
||
|
return translation(v[0], v[1], v[2]);
|
||
|
}
|
||
|
|
||
|
pub fn scaling(x: f32, y: f32, z: f32) Mat {
|
||
|
return .{
|
||
|
f32x4(x, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, y, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, z, 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 1.0),
|
||
|
};
|
||
|
}
|
||
|
pub fn scalingV(v: Vec) Mat {
|
||
|
return scaling(v[0], v[1], v[2]);
|
||
|
}
|
||
|
|
||
|
pub fn lookToLh(eyepos: Vec, eyedir: Vec, updir: Vec) Mat {
|
||
|
const az = normalize3(eyedir);
|
||
|
const ax = normalize3(cross3(updir, az));
|
||
|
const ay = normalize3(cross3(az, ax));
|
||
|
return .{
|
||
|
f32x4(ax[0], ay[0], az[0], 0),
|
||
|
f32x4(ax[1], ay[1], az[1], 0),
|
||
|
f32x4(ax[2], ay[2], az[2], 0),
|
||
|
f32x4(-dot3(ax, eyepos)[0], -dot3(ay, eyepos)[0], -dot3(az, eyepos)[0], 1.0),
|
||
|
};
|
||
|
}
|
||
|
pub fn lookToRh(eyepos: Vec, eyedir: Vec, updir: Vec) Mat {
|
||
|
return lookToLh(eyepos, -eyedir, updir);
|
||
|
}
|
||
|
pub fn lookAtLh(eyepos: Vec, focuspos: Vec, updir: Vec) Mat {
|
||
|
return lookToLh(eyepos, focuspos - eyepos, updir);
|
||
|
}
|
||
|
pub fn lookAtRh(eyepos: Vec, focuspos: Vec, updir: Vec) Mat {
|
||
|
return lookToLh(eyepos, eyepos - focuspos, updir);
|
||
|
}
|
||
|
test "zmath.matrix.lookToLh" {
|
||
|
const m = lookToLh(f32x4(0.0, 0.0, -3.0, 1.0), f32x4(0.0, 0.0, 1.0, 0.0), f32x4(0.0, 1.0, 0.0, 0.0));
|
||
|
try expectVecApproxEqAbs(m[0], f32x4(1.0, 0.0, 0.0, 0.0), 0.001);
|
||
|
try expectVecApproxEqAbs(m[1], f32x4(0.0, 1.0, 0.0, 0.0), 0.001);
|
||
|
try expectVecApproxEqAbs(m[2], f32x4(0.0, 0.0, 1.0, 0.0), 0.001);
|
||
|
try expectVecApproxEqAbs(m[3], f32x4(0.0, 0.0, 3.0, 1.0), 0.001);
|
||
|
}
|
||
|
|
||
|
pub fn perspectiveFovLh(fovy: f32, aspect: f32, near: f32, far: f32) Mat {
|
||
|
const scfov = sincos(0.5 * fovy);
|
||
|
|
||
|
assert(near > 0.0 and far > 0.0);
|
||
|
assert(!math.approxEqAbs(f32, scfov[0], 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, aspect, 0.0, 0.01));
|
||
|
|
||
|
const h = scfov[1] / scfov[0];
|
||
|
const w = h / aspect;
|
||
|
const r = far / (far - near);
|
||
|
return .{
|
||
|
f32x4(w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, r, 1.0),
|
||
|
f32x4(0.0, 0.0, -r * near, 0.0),
|
||
|
};
|
||
|
}
|
||
|
pub fn perspectiveFovRh(fovy: f32, aspect: f32, near: f32, far: f32) Mat {
|
||
|
const scfov = sincos(0.5 * fovy);
|
||
|
|
||
|
assert(near > 0.0 and far > 0.0);
|
||
|
assert(!math.approxEqAbs(f32, scfov[0], 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, aspect, 0.0, 0.01));
|
||
|
|
||
|
const h = scfov[1] / scfov[0];
|
||
|
const w = h / aspect;
|
||
|
const r = far / (near - far);
|
||
|
return .{
|
||
|
f32x4(w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, r, -1.0),
|
||
|
f32x4(0.0, 0.0, r * near, 0.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Produces Z values in [-1.0, 1.0] range (OpenGL defaults)
|
||
|
pub fn perspectiveFovLhGl(fovy: f32, aspect: f32, near: f32, far: f32) Mat {
|
||
|
const scfov = sincos(0.5 * fovy);
|
||
|
|
||
|
assert(near > 0.0 and far > 0.0);
|
||
|
assert(!math.approxEqAbs(f32, scfov[0], 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, aspect, 0.0, 0.01));
|
||
|
|
||
|
const h = scfov[1] / scfov[0];
|
||
|
const w = h / aspect;
|
||
|
const r = far - near;
|
||
|
return .{
|
||
|
f32x4(w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, (near + far) / r, 1.0),
|
||
|
f32x4(0.0, 0.0, 2.0 * near * far / -r, 0.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Produces Z values in [-1.0, 1.0] range (OpenGL defaults)
|
||
|
pub fn perspectiveFovRhGl(fovy: f32, aspect: f32, near: f32, far: f32) Mat {
|
||
|
const scfov = sincos(0.5 * fovy);
|
||
|
|
||
|
assert(near > 0.0 and far > 0.0);
|
||
|
assert(!math.approxEqAbs(f32, scfov[0], 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, aspect, 0.0, 0.01));
|
||
|
|
||
|
const h = scfov[1] / scfov[0];
|
||
|
const w = h / aspect;
|
||
|
const r = near - far;
|
||
|
return .{
|
||
|
f32x4(w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, (near + far) / r, -1.0),
|
||
|
f32x4(0.0, 0.0, 2.0 * near * far / r, 0.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn orthographicLh(w: f32, h: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, w, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, h, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = 1 / (far - near);
|
||
|
return .{
|
||
|
f32x4(2 / w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, r, 0.0),
|
||
|
f32x4(0.0, 0.0, -r * near, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn orthographicRh(w: f32, h: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, w, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, h, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = 1 / (near - far);
|
||
|
return .{
|
||
|
f32x4(2 / w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, r, 0.0),
|
||
|
f32x4(0.0, 0.0, r * near, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Produces Z values in [-1.0, 1.0] range (OpenGL defaults)
|
||
|
pub fn orthographicLhGl(w: f32, h: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, w, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, h, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = far - near;
|
||
|
return .{
|
||
|
f32x4(2 / w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 2 / r, 0.0),
|
||
|
f32x4(0.0, 0.0, (near + far) / -r, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Produces Z values in [-1.0, 1.0] range (OpenGL defaults)
|
||
|
pub fn orthographicRhGl(w: f32, h: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, w, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, h, 0.0, 0.001));
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = near - far;
|
||
|
return .{
|
||
|
f32x4(2 / w, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / h, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 2 / r, 0.0),
|
||
|
f32x4(0.0, 0.0, (near + far) / r, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn orthographicOffCenterLh(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = 1 / (far - near);
|
||
|
return .{
|
||
|
f32x4(2 / (right - left), 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / (top - bottom), 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, r, 0.0),
|
||
|
f32x4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), -r * near, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn orthographicOffCenterRh(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = 1 / (near - far);
|
||
|
return .{
|
||
|
f32x4(2 / (right - left), 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / (top - bottom), 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, r, 0.0),
|
||
|
f32x4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), r * near, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Produces Z values in [-1.0, 1.0] range (OpenGL defaults)
|
||
|
pub fn orthographicOffCenterLhGl(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = far - near;
|
||
|
return .{
|
||
|
f32x4(2 / (right - left), 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / (top - bottom), 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 2 / r, 0.0),
|
||
|
f32x4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), (near + far) / -r, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Produces Z values in [-1.0, 1.0] range (OpenGL defaults)
|
||
|
pub fn orthographicOffCenterRhGl(left: f32, right: f32, top: f32, bottom: f32, near: f32, far: f32) Mat {
|
||
|
assert(!math.approxEqAbs(f32, far, near, 0.001));
|
||
|
|
||
|
const r = near - far;
|
||
|
return .{
|
||
|
f32x4(2 / (right - left), 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 2 / (top - bottom), 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 2 / r, 0.0),
|
||
|
f32x4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), (near + far) / r, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn determinant(m: Mat) F32x4 {
|
||
|
var v0 = swizzle(m[2], .y, .x, .x, .x);
|
||
|
var v1 = swizzle(m[3], .z, .z, .y, .y);
|
||
|
var v2 = swizzle(m[2], .y, .x, .x, .x);
|
||
|
var v3 = swizzle(m[3], .w, .w, .w, .z);
|
||
|
var v4 = swizzle(m[2], .z, .z, .y, .y);
|
||
|
var v5 = swizzle(m[3], .w, .w, .w, .z);
|
||
|
|
||
|
var p0 = v0 * v1;
|
||
|
var p1 = v2 * v3;
|
||
|
var p2 = v4 * v5;
|
||
|
|
||
|
v0 = swizzle(m[2], .z, .z, .y, .y);
|
||
|
v1 = swizzle(m[3], .y, .x, .x, .x);
|
||
|
v2 = swizzle(m[2], .w, .w, .w, .z);
|
||
|
v3 = swizzle(m[3], .y, .x, .x, .x);
|
||
|
v4 = swizzle(m[2], .w, .w, .w, .z);
|
||
|
v5 = swizzle(m[3], .z, .z, .y, .y);
|
||
|
|
||
|
p0 = mulAdd(-v0, v1, p0);
|
||
|
p1 = mulAdd(-v2, v3, p1);
|
||
|
p2 = mulAdd(-v4, v5, p2);
|
||
|
|
||
|
v0 = swizzle(m[1], .w, .w, .w, .z);
|
||
|
v1 = swizzle(m[1], .z, .z, .y, .y);
|
||
|
v2 = swizzle(m[1], .y, .x, .x, .x);
|
||
|
|
||
|
const s = m[0] * f32x4(1.0, -1.0, 1.0, -1.0);
|
||
|
var r = v0 * p0;
|
||
|
r = mulAdd(-v1, p1, r);
|
||
|
r = mulAdd(v2, p2, r);
|
||
|
return dot4(s, r);
|
||
|
}
|
||
|
test "zmath.matrix.determinant" {
|
||
|
const m = Mat{
|
||
|
f32x4(10.0, -9.0, -12.0, 1.0),
|
||
|
f32x4(7.0, -12.0, 11.0, 1.0),
|
||
|
f32x4(-10.0, 10.0, 3.0, 1.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0),
|
||
|
};
|
||
|
try expectVecApproxEqAbs(determinant(m), splat(F32x4, 2939.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub fn inverse(a: anytype) @TypeOf(a) {
|
||
|
const T = @TypeOf(a);
|
||
|
return switch (T) {
|
||
|
Mat => inverseMat(a),
|
||
|
Quat => inverseQuat(a),
|
||
|
else => @compileError("zmath.inverse() not implemented for " ++ @typeName(T)),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn inverseMat(m: Mat) Mat {
|
||
|
return inverseDet(m, null);
|
||
|
}
|
||
|
|
||
|
pub fn inverseDet(m: Mat, out_det: ?*F32x4) Mat {
|
||
|
const mt = transpose(m);
|
||
|
var v0: [4]F32x4 = undefined;
|
||
|
var v1: [4]F32x4 = undefined;
|
||
|
|
||
|
v0[0] = swizzle(mt[2], .x, .x, .y, .y);
|
||
|
v1[0] = swizzle(mt[3], .z, .w, .z, .w);
|
||
|
v0[1] = swizzle(mt[0], .x, .x, .y, .y);
|
||
|
v1[1] = swizzle(mt[1], .z, .w, .z, .w);
|
||
|
v0[2] = @shuffle(f32, mt[2], mt[0], [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) });
|
||
|
v1[2] = @shuffle(f32, mt[3], mt[1], [4]i32{ 1, 3, ~@as(i32, 1), ~@as(i32, 3) });
|
||
|
|
||
|
var d0 = v0[0] * v1[0];
|
||
|
var d1 = v0[1] * v1[1];
|
||
|
var d2 = v0[2] * v1[2];
|
||
|
|
||
|
v0[0] = swizzle(mt[2], .z, .w, .z, .w);
|
||
|
v1[0] = swizzle(mt[3], .x, .x, .y, .y);
|
||
|
v0[1] = swizzle(mt[0], .z, .w, .z, .w);
|
||
|
v1[1] = swizzle(mt[1], .x, .x, .y, .y);
|
||
|
v0[2] = @shuffle(f32, mt[2], mt[0], [4]i32{ 1, 3, ~@as(i32, 1), ~@as(i32, 3) });
|
||
|
v1[2] = @shuffle(f32, mt[3], mt[1], [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) });
|
||
|
|
||
|
d0 = mulAdd(-v0[0], v1[0], d0);
|
||
|
d1 = mulAdd(-v0[1], v1[1], d1);
|
||
|
d2 = mulAdd(-v0[2], v1[2], d2);
|
||
|
|
||
|
v0[0] = swizzle(mt[1], .y, .z, .x, .y);
|
||
|
v1[0] = @shuffle(f32, d0, d2, [4]i32{ ~@as(i32, 1), 1, 3, 0 });
|
||
|
v0[1] = swizzle(mt[0], .z, .x, .y, .x);
|
||
|
v1[1] = @shuffle(f32, d0, d2, [4]i32{ 3, ~@as(i32, 1), 1, 2 });
|
||
|
v0[2] = swizzle(mt[3], .y, .z, .x, .y);
|
||
|
v1[2] = @shuffle(f32, d1, d2, [4]i32{ ~@as(i32, 3), 1, 3, 0 });
|
||
|
v0[3] = swizzle(mt[2], .z, .x, .y, .x);
|
||
|
v1[3] = @shuffle(f32, d1, d2, [4]i32{ 3, ~@as(i32, 3), 1, 2 });
|
||
|
|
||
|
var c0 = v0[0] * v1[0];
|
||
|
var c2 = v0[1] * v1[1];
|
||
|
var c4 = v0[2] * v1[2];
|
||
|
var c6 = v0[3] * v1[3];
|
||
|
|
||
|
v0[0] = swizzle(mt[1], .z, .w, .y, .z);
|
||
|
v1[0] = @shuffle(f32, d0, d2, [4]i32{ 3, 0, 1, ~@as(i32, 0) });
|
||
|
v0[1] = swizzle(mt[0], .w, .z, .w, .y);
|
||
|
v1[1] = @shuffle(f32, d0, d2, [4]i32{ 2, 1, ~@as(i32, 0), 0 });
|
||
|
v0[2] = swizzle(mt[3], .z, .w, .y, .z);
|
||
|
v1[2] = @shuffle(f32, d1, d2, [4]i32{ 3, 0, 1, ~@as(i32, 2) });
|
||
|
v0[3] = swizzle(mt[2], .w, .z, .w, .y);
|
||
|
v1[3] = @shuffle(f32, d1, d2, [4]i32{ 2, 1, ~@as(i32, 2), 0 });
|
||
|
|
||
|
c0 = mulAdd(-v0[0], v1[0], c0);
|
||
|
c2 = mulAdd(-v0[1], v1[1], c2);
|
||
|
c4 = mulAdd(-v0[2], v1[2], c4);
|
||
|
c6 = mulAdd(-v0[3], v1[3], c6);
|
||
|
|
||
|
v0[0] = swizzle(mt[1], .w, .x, .w, .x);
|
||
|
v1[0] = @shuffle(f32, d0, d2, [4]i32{ 2, ~@as(i32, 1), ~@as(i32, 0), 2 });
|
||
|
v0[1] = swizzle(mt[0], .y, .w, .x, .z);
|
||
|
v1[1] = @shuffle(f32, d0, d2, [4]i32{ ~@as(i32, 1), 0, 3, ~@as(i32, 0) });
|
||
|
v0[2] = swizzle(mt[3], .w, .x, .w, .x);
|
||
|
v1[2] = @shuffle(f32, d1, d2, [4]i32{ 2, ~@as(i32, 3), ~@as(i32, 2), 2 });
|
||
|
v0[3] = swizzle(mt[2], .y, .w, .x, .z);
|
||
|
v1[3] = @shuffle(f32, d1, d2, [4]i32{ ~@as(i32, 3), 0, 3, ~@as(i32, 2) });
|
||
|
|
||
|
const c1 = mulAdd(-v0[0], v1[0], c0);
|
||
|
const c3 = mulAdd(v0[1], v1[1], c2);
|
||
|
const c5 = mulAdd(-v0[2], v1[2], c4);
|
||
|
const c7 = mulAdd(v0[3], v1[3], c6);
|
||
|
|
||
|
c0 = mulAdd(v0[0], v1[0], c0);
|
||
|
c2 = mulAdd(-v0[1], v1[1], c2);
|
||
|
c4 = mulAdd(v0[2], v1[2], c4);
|
||
|
c6 = mulAdd(-v0[3], v1[3], c6);
|
||
|
|
||
|
var mr = Mat{
|
||
|
f32x4(c0[0], c1[1], c0[2], c1[3]),
|
||
|
f32x4(c2[0], c3[1], c2[2], c3[3]),
|
||
|
f32x4(c4[0], c5[1], c4[2], c5[3]),
|
||
|
f32x4(c6[0], c7[1], c6[2], c7[3]),
|
||
|
};
|
||
|
|
||
|
const det = dot4(mr[0], mt[0]);
|
||
|
if (out_det != null) {
|
||
|
out_det.?.* = det;
|
||
|
}
|
||
|
|
||
|
if (math.approxEqAbs(f32, det[0], 0.0, math.floatEps(f32))) {
|
||
|
return .{
|
||
|
f32x4(0.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 0.0),
|
||
|
f32x4(0.0, 0.0, 0.0, 0.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
const scale = splat(F32x4, 1.0) / det;
|
||
|
mr[0] *= scale;
|
||
|
mr[1] *= scale;
|
||
|
mr[2] *= scale;
|
||
|
mr[3] *= scale;
|
||
|
return mr;
|
||
|
}
|
||
|
test "zmath.matrix.inverse" {
|
||
|
const m = Mat{
|
||
|
f32x4(10.0, -9.0, -12.0, 1.0),
|
||
|
f32x4(7.0, -12.0, 11.0, 1.0),
|
||
|
f32x4(-10.0, 10.0, 3.0, 1.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0),
|
||
|
};
|
||
|
var det: F32x4 = undefined;
|
||
|
const mi = inverseDet(m, &det);
|
||
|
try expectVecApproxEqAbs(det, splat(F32x4, 2939.0), 0.0001);
|
||
|
|
||
|
try expectVecApproxEqAbs(mi[0], f32x4(-0.170806, -0.13576, -0.349439, 0.164001), 0.0001);
|
||
|
try expectVecApproxEqAbs(mi[1], f32x4(-0.163661, -0.14801, -0.253147, 0.141204), 0.0001);
|
||
|
try expectVecApproxEqAbs(mi[2], f32x4(-0.0871045, 0.00646478, -0.0785982, 0.0398095), 0.0001);
|
||
|
try expectVecApproxEqAbs(mi[3], f32x4(0.18986, 0.103096, 0.272882, 0.10854), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub fn matFromNormAxisAngle(axis: Vec, angle: f32) Mat {
|
||
|
const sincos_angle = sincos(angle);
|
||
|
|
||
|
const c2 = splat(F32x4, 1.0 - sincos_angle[1]);
|
||
|
const c1 = splat(F32x4, sincos_angle[1]);
|
||
|
const c0 = splat(F32x4, sincos_angle[0]);
|
||
|
|
||
|
const n0 = swizzle(axis, .y, .z, .x, .w);
|
||
|
const n1 = swizzle(axis, .z, .x, .y, .w);
|
||
|
|
||
|
var v0 = c2 * n0 * n1;
|
||
|
const r0 = c2 * axis * axis + c1;
|
||
|
const r1 = c0 * axis + v0;
|
||
|
var r2 = v0 - c0 * axis;
|
||
|
|
||
|
v0 = andInt(r0, f32x4_mask3);
|
||
|
|
||
|
var v1 = @shuffle(f32, r1, r2, [4]i32{ 0, 2, ~@as(i32, 1), ~@as(i32, 2) });
|
||
|
v1 = swizzle(v1, .y, .z, .w, .x);
|
||
|
|
||
|
var v2 = @shuffle(f32, r1, r2, [4]i32{ 1, 1, ~@as(i32, 0), ~@as(i32, 0) });
|
||
|
v2 = swizzle(v2, .x, .z, .x, .z);
|
||
|
|
||
|
r2 = @shuffle(f32, v0, v1, [4]i32{ 0, 3, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
r2 = swizzle(r2, .x, .z, .w, .y);
|
||
|
|
||
|
var m: Mat = undefined;
|
||
|
m[0] = r2;
|
||
|
|
||
|
r2 = @shuffle(f32, v0, v1, [4]i32{ 1, 3, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
r2 = swizzle(r2, .z, .x, .w, .y);
|
||
|
m[1] = r2;
|
||
|
|
||
|
v2 = @shuffle(f32, v2, v0, [4]i32{ 0, 1, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
m[2] = v2;
|
||
|
m[3] = f32x4(0.0, 0.0, 0.0, 1.0);
|
||
|
return m;
|
||
|
}
|
||
|
pub fn matFromAxisAngle(axis: Vec, angle: f32) Mat {
|
||
|
assert(!all(axis == splat(F32x4, 0.0), 3));
|
||
|
assert(!all(isInf(axis), 3));
|
||
|
const normal = normalize3(axis);
|
||
|
return matFromNormAxisAngle(normal, angle);
|
||
|
}
|
||
|
test "zmath.matrix.matFromAxisAngle" {
|
||
|
{
|
||
|
const m0 = matFromAxisAngle(f32x4(1.0, 0.0, 0.0, 0.0), math.pi * 0.25);
|
||
|
const m1 = rotationX(math.pi * 0.25);
|
||
|
try expectVecApproxEqAbs(m0[0], m1[0], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[1], m1[1], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[2], m1[2], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[3], m1[3], 0.001);
|
||
|
}
|
||
|
{
|
||
|
const m0 = matFromAxisAngle(f32x4(0.0, 1.0, 0.0, 0.0), math.pi * 0.125);
|
||
|
const m1 = rotationY(math.pi * 0.125);
|
||
|
try expectVecApproxEqAbs(m0[0], m1[0], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[1], m1[1], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[2], m1[2], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[3], m1[3], 0.001);
|
||
|
}
|
||
|
{
|
||
|
const m0 = matFromAxisAngle(f32x4(0.0, 0.0, 1.0, 0.0), math.pi * 0.333);
|
||
|
const m1 = rotationZ(math.pi * 0.333);
|
||
|
try expectVecApproxEqAbs(m0[0], m1[0], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[1], m1[1], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[2], m1[2], 0.001);
|
||
|
try expectVecApproxEqAbs(m0[3], m1[3], 0.001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn matFromQuat(quat: Quat) Mat {
|
||
|
const q0 = quat + quat;
|
||
|
var q1 = quat * q0;
|
||
|
|
||
|
var v0 = swizzle(q1, .y, .x, .x, .w);
|
||
|
v0 = andInt(v0, f32x4_mask3);
|
||
|
|
||
|
var v1 = swizzle(q1, .z, .z, .y, .w);
|
||
|
v1 = andInt(v1, f32x4_mask3);
|
||
|
|
||
|
const r0 = (f32x4(1.0, 1.0, 1.0, 0.0) - v0) - v1;
|
||
|
|
||
|
v0 = swizzle(quat, .x, .x, .y, .w);
|
||
|
v1 = swizzle(q0, .z, .y, .z, .w);
|
||
|
v0 = v0 * v1;
|
||
|
|
||
|
v1 = swizzle(quat, .w, .w, .w, .w);
|
||
|
const v2 = swizzle(q0, .y, .z, .x, .w);
|
||
|
v1 = v1 * v2;
|
||
|
|
||
|
const r1 = v0 + v1;
|
||
|
const r2 = v0 - v1;
|
||
|
|
||
|
v0 = @shuffle(f32, r1, r2, [4]i32{ 1, 2, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
v0 = swizzle(v0, .x, .z, .w, .y);
|
||
|
v1 = @shuffle(f32, r1, r2, [4]i32{ 0, 0, ~@as(i32, 2), ~@as(i32, 2) });
|
||
|
v1 = swizzle(v1, .x, .z, .x, .z);
|
||
|
|
||
|
q1 = @shuffle(f32, r0, v0, [4]i32{ 0, 3, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
q1 = swizzle(q1, .x, .z, .w, .y);
|
||
|
|
||
|
var m: Mat = undefined;
|
||
|
m[0] = q1;
|
||
|
|
||
|
q1 = @shuffle(f32, r0, v0, [4]i32{ 1, 3, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
q1 = swizzle(q1, .z, .x, .w, .y);
|
||
|
m[1] = q1;
|
||
|
|
||
|
q1 = @shuffle(f32, v1, r0, [4]i32{ 0, 1, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
m[2] = q1;
|
||
|
m[3] = f32x4(0.0, 0.0, 0.0, 1.0);
|
||
|
return m;
|
||
|
}
|
||
|
test "zmath.matrix.matFromQuat" {
|
||
|
{
|
||
|
const m = matFromQuat(f32x4(0.0, 0.0, 0.0, 1.0));
|
||
|
try expectVecApproxEqAbs(m[0], f32x4(1.0, 0.0, 0.0, 0.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(m[1], f32x4(0.0, 1.0, 0.0, 0.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(m[2], f32x4(0.0, 0.0, 1.0, 0.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(m[3], f32x4(0.0, 0.0, 0.0, 1.0), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn matFromRollPitchYaw(pitch: f32, yaw: f32, roll: f32) Mat {
|
||
|
return matFromRollPitchYawV(f32x4(pitch, yaw, roll, 0.0));
|
||
|
}
|
||
|
pub fn matFromRollPitchYawV(angles: Vec) Mat {
|
||
|
return matFromQuat(quatFromRollPitchYawV(angles));
|
||
|
}
|
||
|
|
||
|
pub fn matToQuat(m: Mat) Quat {
|
||
|
return quatFromMat(m);
|
||
|
}
|
||
|
|
||
|
pub inline fn loadMat(mem: []const f32) Mat {
|
||
|
return .{
|
||
|
load(mem[0..4], F32x4, 0),
|
||
|
load(mem[4..8], F32x4, 0),
|
||
|
load(mem[8..12], F32x4, 0),
|
||
|
load(mem[12..16], F32x4, 0),
|
||
|
};
|
||
|
}
|
||
|
test "zmath.loadMat" {
|
||
|
const a = [18]f32{
|
||
|
1.0, 2.0, 3.0, 4.0,
|
||
|
5.0, 6.0, 7.0, 8.0,
|
||
|
9.0, 10.0, 11.0, 12.0,
|
||
|
13.0, 14.0, 15.0, 16.0,
|
||
|
17.0, 18.0,
|
||
|
};
|
||
|
const m = loadMat(a[1..]);
|
||
|
try expectVecEqual(m[0], f32x4(2.0, 3.0, 4.0, 5.0));
|
||
|
try expectVecEqual(m[1], f32x4(6.0, 7.0, 8.0, 9.0));
|
||
|
try expectVecEqual(m[2], f32x4(10.0, 11.0, 12.0, 13.0));
|
||
|
try expectVecEqual(m[3], f32x4(14.0, 15.0, 16.0, 17.0));
|
||
|
}
|
||
|
|
||
|
pub inline fn storeMat(mem: []f32, m: Mat) void {
|
||
|
store(mem[0..4], m[0], 0);
|
||
|
store(mem[4..8], m[1], 0);
|
||
|
store(mem[8..12], m[2], 0);
|
||
|
store(mem[12..16], m[3], 0);
|
||
|
}
|
||
|
|
||
|
pub inline fn loadMat43(mem: []const f32) Mat {
|
||
|
return .{
|
||
|
f32x4(mem[0], mem[1], mem[2], 0.0),
|
||
|
f32x4(mem[3], mem[4], mem[5], 0.0),
|
||
|
f32x4(mem[6], mem[7], mem[8], 0.0),
|
||
|
f32x4(mem[9], mem[10], mem[11], 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub inline fn storeMat43(mem: []f32, m: Mat) void {
|
||
|
store(mem[0..3], m[0], 3);
|
||
|
store(mem[3..6], m[1], 3);
|
||
|
store(mem[6..9], m[2], 3);
|
||
|
store(mem[9..12], m[3], 3);
|
||
|
}
|
||
|
|
||
|
pub inline fn loadMat34(mem: []const f32) Mat {
|
||
|
return .{
|
||
|
load(mem[0..4], F32x4, 0),
|
||
|
load(mem[4..8], F32x4, 0),
|
||
|
load(mem[8..12], F32x4, 0),
|
||
|
f32x4(0.0, 0.0, 0.0, 1.0),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub inline fn storeMat34(mem: []f32, m: Mat) void {
|
||
|
store(mem[0..4], m[0], 0);
|
||
|
store(mem[4..8], m[1], 0);
|
||
|
store(mem[8..12], m[2], 0);
|
||
|
}
|
||
|
|
||
|
pub inline fn matToArr(m: Mat) [16]f32 {
|
||
|
var array: [16]f32 = undefined;
|
||
|
storeMat(array[0..], m);
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
pub inline fn matToArr43(m: Mat) [12]f32 {
|
||
|
var array: [12]f32 = undefined;
|
||
|
storeMat43(array[0..], m);
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
pub inline fn matToArr34(m: Mat) [12]f32 {
|
||
|
var array: [12]f32 = undefined;
|
||
|
storeMat34(array[0..], m);
|
||
|
return array;
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 5. Quaternion functions
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub fn qmul(q0: Quat, q1: Quat) Quat {
|
||
|
var result = swizzle(q1, .w, .w, .w, .w);
|
||
|
var q1x = swizzle(q1, .x, .x, .x, .x);
|
||
|
var q1y = swizzle(q1, .y, .y, .y, .y);
|
||
|
var q1z = swizzle(q1, .z, .z, .z, .z);
|
||
|
result = result * q0;
|
||
|
var q0_shuf = swizzle(q0, .w, .z, .y, .x);
|
||
|
q1x = q1x * q0_shuf;
|
||
|
q0_shuf = swizzle(q0_shuf, .y, .x, .w, .z);
|
||
|
result = mulAdd(q1x, f32x4(1.0, -1.0, 1.0, -1.0), result);
|
||
|
q1y = q1y * q0_shuf;
|
||
|
q0_shuf = swizzle(q0_shuf, .w, .z, .y, .x);
|
||
|
q1y = q1y * f32x4(1.0, 1.0, -1.0, -1.0);
|
||
|
q1z = q1z * q0_shuf;
|
||
|
q1y = mulAdd(q1z, f32x4(-1.0, 1.0, 1.0, -1.0), q1y);
|
||
|
return result + q1y;
|
||
|
}
|
||
|
test "zmath.quaternion.mul" {
|
||
|
{
|
||
|
const q0 = f32x4(2.0, 3.0, 4.0, 1.0);
|
||
|
const q1 = f32x4(3.0, 2.0, 1.0, 4.0);
|
||
|
try expectVecApproxEqAbs(qmul(q0, q1), f32x4(16.0, 4.0, 22.0, -12.0), 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn quatToMat(quat: Quat) Mat {
|
||
|
return matFromQuat(quat);
|
||
|
}
|
||
|
|
||
|
pub fn quatToAxisAngle(quat: Quat, axis: *Vec, angle: *f32) void {
|
||
|
axis.* = quat;
|
||
|
angle.* = 2.0 * acos(quat[3]);
|
||
|
}
|
||
|
test "zmath.quaternion.quatToAxisAngle" {
|
||
|
{
|
||
|
const q0 = quatFromNormAxisAngle(f32x4(1.0, 0.0, 0.0, 0.0), 0.25 * math.pi);
|
||
|
var axis: Vec = f32x4(4.0, 3.0, 2.0, 1.0);
|
||
|
var angle: f32 = 10.0;
|
||
|
quatToAxisAngle(q0, &axis, &angle);
|
||
|
try expect(math.approxEqAbs(f32, axis[0], @sin(@as(f32, 0.25) * math.pi * 0.5), 0.0001));
|
||
|
try expect(axis[1] == 0.0);
|
||
|
try expect(axis[2] == 0.0);
|
||
|
try expect(math.approxEqAbs(f32, angle, 0.25 * math.pi, 0.0001));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn quatFromMat(m: Mat) Quat {
|
||
|
const r0 = m[0];
|
||
|
const r1 = m[1];
|
||
|
const r2 = m[2];
|
||
|
const r00 = swizzle(r0, .x, .x, .x, .x);
|
||
|
const r11 = swizzle(r1, .y, .y, .y, .y);
|
||
|
const r22 = swizzle(r2, .z, .z, .z, .z);
|
||
|
|
||
|
const x2gey2 = (r11 - r00) <= splat(F32x4, 0.0);
|
||
|
const z2gew2 = (r11 + r00) <= splat(F32x4, 0.0);
|
||
|
const x2py2gez2pw2 = r22 <= splat(F32x4, 0.0);
|
||
|
|
||
|
var t0 = mulAdd(r00, f32x4(1.0, -1.0, -1.0, 1.0), splat(F32x4, 1.0));
|
||
|
var t1 = r11 * f32x4(-1.0, 1.0, -1.0, 1.0);
|
||
|
var t2 = mulAdd(r22, f32x4(-1.0, -1.0, 1.0, 1.0), t0);
|
||
|
const x2y2z2w2 = t1 + t2;
|
||
|
|
||
|
t0 = @shuffle(f32, r0, r1, [4]i32{ 1, 2, ~@as(i32, 2), ~@as(i32, 1) });
|
||
|
t1 = @shuffle(f32, r1, r2, [4]i32{ 0, 0, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
t1 = swizzle(t1, .x, .z, .w, .y);
|
||
|
const xyxzyz = t0 + t1;
|
||
|
|
||
|
t0 = @shuffle(f32, r2, r1, [4]i32{ 1, 0, ~@as(i32, 0), ~@as(i32, 0) });
|
||
|
t1 = @shuffle(f32, r1, r0, [4]i32{ 2, 2, ~@as(i32, 2), ~@as(i32, 1) });
|
||
|
t1 = swizzle(t1, .x, .z, .w, .y);
|
||
|
const xwywzw = (t0 - t1) * f32x4(-1.0, 1.0, -1.0, 1.0);
|
||
|
|
||
|
t0 = @shuffle(f32, x2y2z2w2, xyxzyz, [4]i32{ 0, 1, ~@as(i32, 0), ~@as(i32, 0) });
|
||
|
t1 = @shuffle(f32, x2y2z2w2, xwywzw, [4]i32{ 2, 3, ~@as(i32, 2), ~@as(i32, 0) });
|
||
|
t2 = @shuffle(f32, xyxzyz, xwywzw, [4]i32{ 1, 2, ~@as(i32, 0), ~@as(i32, 1) });
|
||
|
|
||
|
const tensor0 = @shuffle(f32, t0, t2, [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) });
|
||
|
const tensor1 = @shuffle(f32, t0, t2, [4]i32{ 2, 1, ~@as(i32, 1), ~@as(i32, 3) });
|
||
|
const tensor2 = @shuffle(f32, t2, t1, [4]i32{ 0, 1, ~@as(i32, 0), ~@as(i32, 2) });
|
||
|
const tensor3 = @shuffle(f32, t2, t1, [4]i32{ 2, 3, ~@as(i32, 2), ~@as(i32, 1) });
|
||
|
|
||
|
t0 = select(x2gey2, tensor0, tensor1);
|
||
|
t1 = select(z2gew2, tensor2, tensor3);
|
||
|
t2 = select(x2py2gez2pw2, t0, t1);
|
||
|
|
||
|
return t2 / length4(t2);
|
||
|
}
|
||
|
test "zmath.quatFromMat" {
|
||
|
{
|
||
|
const q0 = quatFromAxisAngle(f32x4(1.0, 0.0, 0.0, 0.0), 0.25 * math.pi);
|
||
|
const q1 = quatFromMat(rotationX(0.25 * math.pi));
|
||
|
try expectVecApproxEqAbs(q0, q1, 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const q0 = quatFromAxisAngle(f32x4(1.0, 2.0, 0.5, 0.0), 0.25 * math.pi);
|
||
|
const q1 = quatFromMat(matFromAxisAngle(f32x4(1.0, 2.0, 0.5, 0.0), 0.25 * math.pi));
|
||
|
try expectVecApproxEqAbs(q0, q1, 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const q0 = quatFromRollPitchYaw(0.1 * math.pi, -0.2 * math.pi, 0.3 * math.pi);
|
||
|
const q1 = quatFromMat(matFromRollPitchYaw(0.1 * math.pi, -0.2 * math.pi, 0.3 * math.pi));
|
||
|
try expectVecApproxEqAbs(q0, q1, 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn quatFromNormAxisAngle(axis: Vec, angle: f32) Quat {
|
||
|
const n = f32x4(axis[0], axis[1], axis[2], 1.0);
|
||
|
const sc = sincos(0.5 * angle);
|
||
|
return n * f32x4(sc[0], sc[0], sc[0], sc[1]);
|
||
|
}
|
||
|
pub fn quatFromAxisAngle(axis: Vec, angle: f32) Quat {
|
||
|
assert(!all(axis == splat(F32x4, 0.0), 3));
|
||
|
assert(!all(isInf(axis), 3));
|
||
|
const normal = normalize3(axis);
|
||
|
return quatFromNormAxisAngle(normal, angle);
|
||
|
}
|
||
|
test "zmath.quaternion.quatFromNormAxisAngle" {
|
||
|
{
|
||
|
const q0 = quatFromAxisAngle(f32x4(1.0, 0.0, 0.0, 0.0), 0.25 * math.pi);
|
||
|
const q1 = quatFromAxisAngle(f32x4(0.0, 1.0, 0.0, 0.0), 0.125 * math.pi);
|
||
|
const m0 = rotationX(0.25 * math.pi);
|
||
|
const m1 = rotationY(0.125 * math.pi);
|
||
|
const mr0 = quatToMat(qmul(q0, q1));
|
||
|
const mr1 = mul(m0, m1);
|
||
|
try expectVecApproxEqAbs(mr0[0], mr1[0], 0.0001);
|
||
|
try expectVecApproxEqAbs(mr0[1], mr1[1], 0.0001);
|
||
|
try expectVecApproxEqAbs(mr0[2], mr1[2], 0.0001);
|
||
|
try expectVecApproxEqAbs(mr0[3], mr1[3], 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const m0 = quatToMat(quatFromAxisAngle(f32x4(1.0, 2.0, 0.5, 0.0), 0.25 * math.pi));
|
||
|
const m1 = matFromAxisAngle(f32x4(1.0, 2.0, 0.5, 0.0), 0.25 * math.pi);
|
||
|
try expectVecApproxEqAbs(m0[0], m1[0], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[1], m1[1], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[2], m1[2], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[3], m1[3], 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub inline fn qidentity() Quat {
|
||
|
return f32x4(@as(f32, 0.0), @as(f32, 0.0), @as(f32, 0.0), @as(f32, 1.0));
|
||
|
}
|
||
|
|
||
|
pub inline fn conjugate(quat: Quat) Quat {
|
||
|
return quat * f32x4(-1.0, -1.0, -1.0, 1.0);
|
||
|
}
|
||
|
|
||
|
fn inverseQuat(quat: Quat) Quat {
|
||
|
const l = lengthSq4(quat);
|
||
|
const conj = conjugate(quat);
|
||
|
return select(l <= splat(F32x4, math.floatEps(f32)), splat(F32x4, 0.0), conj / l);
|
||
|
}
|
||
|
test "zmath.quaternion.inverseQuat" {
|
||
|
try expectVecApproxEqAbs(
|
||
|
inverse(f32x4(2.0, 3.0, 4.0, 1.0)),
|
||
|
f32x4(-1.0 / 15.0, -1.0 / 10.0, -2.0 / 15.0, 1.0 / 30.0),
|
||
|
0.0001,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(inverse(qidentity()), qidentity(), 0.0001);
|
||
|
}
|
||
|
|
||
|
// Algorithm from: https://github.com/g-truc/glm/blob/master/glm/detail/type_quat.inl
|
||
|
pub fn rotate(q: Quat, v: Vec) Vec {
|
||
|
const w = splat(F32x4, q[3]);
|
||
|
const axis = f32x4(q[0], q[1], q[2], 0.0);
|
||
|
const uv = cross3(axis, v);
|
||
|
return v + ((uv * w) + cross3(axis, uv)) * splat(F32x4, 2.0);
|
||
|
}
|
||
|
test "zmath.quaternion.rotate" {
|
||
|
const quat = quatFromRollPitchYaw(0.1 * math.pi, 0.2 * math.pi, 0.3 * math.pi);
|
||
|
const mat = matFromQuat(quat);
|
||
|
const forward = f32x4(0.0, 0.0, -1.0, 0.0);
|
||
|
const up = f32x4(0.0, 1.0, 0.0, 0.0);
|
||
|
const right = f32x4(1.0, 0.0, 0.0, 0.0);
|
||
|
try expectVecApproxEqAbs(rotate(quat, forward), mul(forward, mat), 0.0001);
|
||
|
try expectVecApproxEqAbs(rotate(quat, up), mul(up, mat), 0.0001);
|
||
|
try expectVecApproxEqAbs(rotate(quat, right), mul(right, mat), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub fn slerp(q0: Quat, q1: Quat, t: f32) Quat {
|
||
|
return slerpV(q0, q1, splat(F32x4, t));
|
||
|
}
|
||
|
pub fn slerpV(q0: Quat, q1: Quat, t: F32x4) Quat {
|
||
|
var cos_omega = dot4(q0, q1);
|
||
|
const sign = select(cos_omega < splat(F32x4, 0.0), splat(F32x4, -1.0), splat(F32x4, 1.0));
|
||
|
|
||
|
cos_omega = cos_omega * sign;
|
||
|
const sin_omega = sqrt(splat(F32x4, 1.0) - cos_omega * cos_omega);
|
||
|
|
||
|
const omega = atan2(sin_omega, cos_omega);
|
||
|
|
||
|
var v01 = t;
|
||
|
v01 = xorInt(andInt(v01, f32x4_mask2), f32x4_sign_mask1);
|
||
|
v01 = f32x4(1.0, 0.0, 0.0, 0.0) + v01;
|
||
|
|
||
|
var s0 = sin(v01 * omega) / sin_omega;
|
||
|
s0 = select(cos_omega < splat(F32x4, 1.0 - 0.00001), s0, v01);
|
||
|
|
||
|
const s1 = swizzle(s0, .y, .y, .y, .y);
|
||
|
s0 = swizzle(s0, .x, .x, .x, .x);
|
||
|
|
||
|
return q0 * s0 + sign * q1 * s1;
|
||
|
}
|
||
|
test "zmath.quaternion.slerp" {
|
||
|
const from = f32x4(0.0, 0.0, 0.0, 1.0);
|
||
|
const to = f32x4(0.5, 0.5, -0.5, 0.5);
|
||
|
const result = slerp(from, to, 0.5);
|
||
|
try expectVecApproxEqAbs(result, f32x4(0.28867513, 0.28867513, -0.28867513, 0.86602540), 0.0001);
|
||
|
}
|
||
|
|
||
|
// Converts q back to euler angles, assuming a YXZ rotation order.
|
||
|
// See: http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler
|
||
|
pub fn quatToRollPitchYaw(q: Quat) [3]f32 {
|
||
|
var angles: [3]f32 = undefined;
|
||
|
|
||
|
const p = swizzle(q, .w, .y, .x, .z);
|
||
|
const sign = -1.0;
|
||
|
|
||
|
const singularity = p[0] * p[2] + sign * p[1] * p[3];
|
||
|
if (singularity > 0.499) {
|
||
|
angles[0] = math.pi * 0.5;
|
||
|
angles[1] = 2.0 * math.atan2(p[1], p[0]);
|
||
|
angles[2] = 0.0;
|
||
|
} else if (singularity < -0.499) {
|
||
|
angles[0] = -math.pi * 0.5;
|
||
|
angles[1] = 2.0 * math.atan2(p[1], p[0]);
|
||
|
angles[2] = 0.0;
|
||
|
} else {
|
||
|
const sq = p * p;
|
||
|
const y = splat(F32x4, 2.0) * f32x4(p[0] * p[1] - sign * p[2] * p[3], p[0] * p[3] - sign * p[1] * p[2], 0.0, 0.0);
|
||
|
const x = splat(F32x4, 1.0) - (splat(F32x4, 2.0) * f32x4(sq[1] + sq[2], sq[2] + sq[3], 0.0, 0.0));
|
||
|
const res = atan2(y, x);
|
||
|
angles[0] = math.asin(2.0 * singularity);
|
||
|
angles[1] = res[0];
|
||
|
angles[2] = res[1];
|
||
|
}
|
||
|
|
||
|
return angles;
|
||
|
}
|
||
|
|
||
|
test "zmath.quaternion.quatToRollPitchYaw" {
|
||
|
{
|
||
|
const expected = f32x4(0.1 * math.pi, 0.2 * math.pi, 0.3 * math.pi, 0.0);
|
||
|
const quat = quatFromRollPitchYaw(expected[0], expected[1], expected[2]);
|
||
|
const result = quatToRollPitchYaw(quat);
|
||
|
try expectVecApproxEqAbs(loadArr3(result), expected, 0.0001);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
const expected = f32x4(0.3 * math.pi, 0.1 * math.pi, 0.2 * math.pi, 0.0);
|
||
|
const quat = quatFromRollPitchYaw(expected[0], expected[1], expected[2]);
|
||
|
const result = quatToRollPitchYaw(quat);
|
||
|
try expectVecApproxEqAbs(loadArr3(result), expected, 0.0001);
|
||
|
}
|
||
|
|
||
|
// North pole singularity
|
||
|
{
|
||
|
const angle = f32x4(0.5 * math.pi, 0.2 * math.pi, 0.3 * math.pi, 0.0);
|
||
|
const expected = f32x4(0.5 * math.pi, -0.1 * math.pi, 0.0, 0.0);
|
||
|
const quat = quatFromRollPitchYaw(angle[0], angle[1], angle[2]);
|
||
|
const result = quatToRollPitchYaw(quat);
|
||
|
try expectVecApproxEqAbs(loadArr3(result), expected, 0.0001);
|
||
|
}
|
||
|
|
||
|
// South pole singularity
|
||
|
{
|
||
|
const angle = f32x4(-0.5 * math.pi, 0.2 * math.pi, 0.3 * math.pi, 0.0);
|
||
|
const expected = f32x4(-0.5 * math.pi, 0.5 * math.pi, 0.0, 0.0);
|
||
|
const quat = quatFromRollPitchYaw(angle[0], angle[1], angle[2]);
|
||
|
const result = quatToRollPitchYaw(quat);
|
||
|
try expectVecApproxEqAbs(loadArr3(result), expected, 0.0001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn quatFromRollPitchYaw(pitch: f32, yaw: f32, roll: f32) Quat {
|
||
|
return quatFromRollPitchYawV(f32x4(pitch, yaw, roll, 0.0));
|
||
|
}
|
||
|
pub fn quatFromRollPitchYawV(angles: Vec) Quat { // | pitch | yaw | roll | 0 |
|
||
|
const sc = sincos(splat(Vec, 0.5) * angles);
|
||
|
const p0 = @shuffle(f32, sc[1], sc[0], [4]i32{ ~@as(i32, 0), 0, 0, 0 });
|
||
|
const p1 = @shuffle(f32, sc[0], sc[1], [4]i32{ ~@as(i32, 0), 0, 0, 0 });
|
||
|
const y0 = @shuffle(f32, sc[1], sc[0], [4]i32{ 1, ~@as(i32, 1), 1, 1 });
|
||
|
const y1 = @shuffle(f32, sc[0], sc[1], [4]i32{ 1, ~@as(i32, 1), 1, 1 });
|
||
|
const r0 = @shuffle(f32, sc[1], sc[0], [4]i32{ 2, 2, ~@as(i32, 2), 2 });
|
||
|
const r1 = @shuffle(f32, sc[0], sc[1], [4]i32{ 2, 2, ~@as(i32, 2), 2 });
|
||
|
const q1 = p1 * f32x4(1.0, -1.0, -1.0, 1.0) * y1;
|
||
|
const q0 = p0 * y0 * r0;
|
||
|
return mulAdd(q1, r1, q0);
|
||
|
}
|
||
|
test "zmath.quaternion.quatFromRollPitchYawV" {
|
||
|
{
|
||
|
const m0 = quatToMat(quatFromRollPitchYawV(f32x4(0.25 * math.pi, 0.0, 0.0, 0.0)));
|
||
|
const m1 = rotationX(0.25 * math.pi);
|
||
|
try expectVecApproxEqAbs(m0[0], m1[0], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[1], m1[1], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[2], m1[2], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[3], m1[3], 0.0001);
|
||
|
}
|
||
|
{
|
||
|
const m0 = quatToMat(quatFromRollPitchYaw(0.1 * math.pi, 0.2 * math.pi, 0.3 * math.pi));
|
||
|
const m1 = mul(
|
||
|
rotationZ(0.3 * math.pi),
|
||
|
mul(rotationX(0.1 * math.pi), rotationY(0.2 * math.pi)),
|
||
|
);
|
||
|
try expectVecApproxEqAbs(m0[0], m1[0], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[1], m1[1], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[2], m1[2], 0.0001);
|
||
|
try expectVecApproxEqAbs(m0[3], m1[3], 0.0001);
|
||
|
}
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 6. Color functions
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub fn adjustSaturation(color: F32x4, saturation: f32) F32x4 {
|
||
|
const luminance = dot3(f32x4(0.2125, 0.7154, 0.0721, 0.0), color);
|
||
|
var result = mulAdd(color - luminance, f32x4s(saturation), luminance);
|
||
|
result[3] = color[3];
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
pub fn adjustContrast(color: F32x4, contrast: f32) F32x4 {
|
||
|
var result = mulAdd(color - f32x4s(0.5), f32x4s(contrast), f32x4s(0.5));
|
||
|
result[3] = color[3];
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
pub fn rgbToHsl(rgb: F32x4) F32x4 {
|
||
|
const r = swizzle(rgb, .x, .x, .x, .x);
|
||
|
const g = swizzle(rgb, .y, .y, .y, .y);
|
||
|
const b = swizzle(rgb, .z, .z, .z, .z);
|
||
|
|
||
|
const minv = min(r, min(g, b));
|
||
|
const maxv = max(r, max(g, b));
|
||
|
|
||
|
const l = (minv + maxv) * f32x4s(0.5);
|
||
|
const d = maxv - minv;
|
||
|
const la = select(boolx4(true, true, true, false), l, rgb);
|
||
|
|
||
|
if (all(d < f32x4s(math.floatEps(f32)), 3)) {
|
||
|
return select(boolx4(true, true, false, false), f32x4s(0.0), la);
|
||
|
} else {
|
||
|
var s: F32x4 = undefined;
|
||
|
var h: F32x4 = undefined;
|
||
|
|
||
|
const d2 = minv + maxv;
|
||
|
|
||
|
if (all(l > f32x4s(0.5), 3)) {
|
||
|
s = d / (f32x4s(2.0) - d2);
|
||
|
} else {
|
||
|
s = d / d2;
|
||
|
}
|
||
|
|
||
|
if (all(r == maxv, 3)) {
|
||
|
h = (g - b) / d;
|
||
|
} else if (all(g == maxv, 3)) {
|
||
|
h = f32x4s(2.0) + (b - r) / d;
|
||
|
} else {
|
||
|
h = f32x4s(4.0) + (r - g) / d;
|
||
|
}
|
||
|
|
||
|
h /= f32x4s(6.0);
|
||
|
|
||
|
if (all(h < f32x4s(0.0), 3)) {
|
||
|
h += f32x4s(1.0);
|
||
|
}
|
||
|
|
||
|
const lha = select(boolx4(true, true, false, false), h, la);
|
||
|
return select(boolx4(true, false, true, true), lha, s);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.color.rgbToHsl" {
|
||
|
try expectVecApproxEqAbs(rgbToHsl(f32x4(0.2, 0.4, 0.8, 1.0)), f32x4(0.6111, 0.6, 0.5, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsl(f32x4(1.0, 0.0, 0.0, 0.5)), f32x4(0.0, 1.0, 0.5, 0.5), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsl(f32x4(0.0, 1.0, 0.0, 0.25)), f32x4(0.3333, 1.0, 0.5, 0.25), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsl(f32x4(0.0, 0.0, 1.0, 1.0)), f32x4(0.6666, 1.0, 0.5, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsl(f32x4(0.0, 0.0, 0.0, 1.0)), f32x4(0.0, 0.0, 0.0, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsl(f32x4(1.0, 1.0, 1.0, 1.0)), f32x4(0.0, 0.0, 1.0, 1.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
fn hueToClr(p: F32x4, q: F32x4, h: F32x4) F32x4 {
|
||
|
var t = h;
|
||
|
|
||
|
if (all(t < f32x4s(0.0), 3))
|
||
|
t += f32x4s(1.0);
|
||
|
|
||
|
if (all(t > f32x4s(1.0), 3))
|
||
|
t -= f32x4s(1.0);
|
||
|
|
||
|
if (all(t < f32x4s(1.0 / 6.0), 3))
|
||
|
return mulAdd(q - p, f32x4s(6.0) * t, p);
|
||
|
|
||
|
if (all(t < f32x4s(0.5), 3))
|
||
|
return q;
|
||
|
|
||
|
if (all(t < f32x4s(2.0 / 3.0), 3))
|
||
|
return mulAdd(q - p, f32x4s(6.0) * (f32x4s(2.0 / 3.0) - t), p);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
pub fn hslToRgb(hsl: F32x4) F32x4 {
|
||
|
const s = swizzle(hsl, .y, .y, .y, .y);
|
||
|
const l = swizzle(hsl, .z, .z, .z, .z);
|
||
|
|
||
|
if (all(isNearEqual(s, f32x4s(0.0), f32x4s(math.floatEps(f32))), 3)) {
|
||
|
return select(boolx4(true, true, true, false), l, hsl);
|
||
|
} else {
|
||
|
const h = swizzle(hsl, .x, .x, .x, .x);
|
||
|
var q: F32x4 = undefined;
|
||
|
if (all(l < f32x4s(0.5), 3)) {
|
||
|
q = l * (f32x4s(1.0) + s);
|
||
|
} else {
|
||
|
q = (l + s) - (l * s);
|
||
|
}
|
||
|
|
||
|
const p = f32x4s(2.0) * l - q;
|
||
|
|
||
|
const r = hueToClr(p, q, h + f32x4s(1.0 / 3.0));
|
||
|
const g = hueToClr(p, q, h);
|
||
|
const b = hueToClr(p, q, h - f32x4s(1.0 / 3.0));
|
||
|
|
||
|
const rg = select(boolx4(true, false, false, false), r, g);
|
||
|
const ba = select(boolx4(true, true, true, false), b, hsl);
|
||
|
return select(boolx4(true, true, false, false), rg, ba);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.color.hslToRgb" {
|
||
|
try expectVecApproxEqAbs(f32x4(0.2, 0.4, 0.8, 1.0), hslToRgb(f32x4(0.6111, 0.6, 0.5, 1.0)), 0.0001);
|
||
|
try expectVecApproxEqAbs(f32x4(1.0, 0.0, 0.0, 0.5), hslToRgb(f32x4(0.0, 1.0, 0.5, 0.5)), 0.0001);
|
||
|
try expectVecApproxEqAbs(f32x4(0.0, 1.0, 0.0, 0.25), hslToRgb(f32x4(0.3333, 1.0, 0.5, 0.25)), 0.0005);
|
||
|
try expectVecApproxEqAbs(f32x4(0.0, 0.0, 1.0, 1.0), hslToRgb(f32x4(0.6666, 1.0, 0.5, 1.0)), 0.0005);
|
||
|
try expectVecApproxEqAbs(f32x4(0.0, 0.0, 0.0, 1.0), hslToRgb(f32x4(0.0, 0.0, 0.0, 1.0)), 0.0001);
|
||
|
try expectVecApproxEqAbs(f32x4(1.0, 1.0, 1.0, 1.0), hslToRgb(f32x4(0.0, 0.0, 1.0, 1.0)), 0.0001);
|
||
|
try expectVecApproxEqAbs(hslToRgb(rgbToHsl(f32x4(1.0, 1.0, 1.0, 1.0))), f32x4(1.0, 1.0, 1.0, 1.0), 0.0005);
|
||
|
try expectVecApproxEqAbs(
|
||
|
hslToRgb(rgbToHsl(f32x4(0.82198, 0.1839, 0.632, 1.0))),
|
||
|
f32x4(0.82198, 0.1839, 0.632, 1.0),
|
||
|
0.0005,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
rgbToHsl(hslToRgb(f32x4(0.82198, 0.1839, 0.632, 1.0))),
|
||
|
f32x4(0.82198, 0.1839, 0.632, 1.0),
|
||
|
0.0005,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
rgbToHsl(hslToRgb(f32x4(0.1839, 0.82198, 0.632, 1.0))),
|
||
|
f32x4(0.1839, 0.82198, 0.632, 1.0),
|
||
|
0.0005,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
hslToRgb(rgbToHsl(f32x4(0.1839, 0.632, 0.82198, 1.0))),
|
||
|
f32x4(0.1839, 0.632, 0.82198, 1.0),
|
||
|
0.0005,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
pub fn rgbToHsv(rgb: F32x4) F32x4 {
|
||
|
const r = swizzle(rgb, .x, .x, .x, .x);
|
||
|
const g = swizzle(rgb, .y, .y, .y, .y);
|
||
|
const b = swizzle(rgb, .z, .z, .z, .z);
|
||
|
|
||
|
const minv = min(r, min(g, b));
|
||
|
const v = max(r, max(g, b));
|
||
|
const d = v - minv;
|
||
|
const s = if (all(isNearEqual(v, f32x4s(0.0), f32x4s(math.floatEps(f32))), 3)) f32x4s(0.0) else d / v;
|
||
|
|
||
|
if (all(d < f32x4s(math.floatEps(f32)), 3)) {
|
||
|
const hv = select(boolx4(true, false, false, false), f32x4s(0.0), v);
|
||
|
const hva = select(boolx4(true, true, true, false), hv, rgb);
|
||
|
return select(boolx4(true, false, true, true), hva, s);
|
||
|
} else {
|
||
|
var h: F32x4 = undefined;
|
||
|
if (all(r == v, 3)) {
|
||
|
h = (g - b) / d;
|
||
|
if (all(g < b, 3))
|
||
|
h += f32x4s(6.0);
|
||
|
} else if (all(g == v, 3)) {
|
||
|
h = f32x4s(2.0) + (b - r) / d;
|
||
|
} else {
|
||
|
h = f32x4s(4.0) + (r - g) / d;
|
||
|
}
|
||
|
|
||
|
h /= f32x4s(6.0);
|
||
|
const hv = select(boolx4(true, false, false, false), h, v);
|
||
|
const hva = select(boolx4(true, true, true, false), hv, rgb);
|
||
|
return select(boolx4(true, false, true, true), hva, s);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.color.rgbToHsv" {
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(0.2, 0.4, 0.8, 1.0)), f32x4(0.6111, 0.75, 0.8, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(0.4, 0.2, 0.8, 1.0)), f32x4(0.7222, 0.75, 0.8, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(0.4, 0.8, 0.2, 1.0)), f32x4(0.2777, 0.75, 0.8, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(1.0, 0.0, 0.0, 0.5)), f32x4(0.0, 1.0, 1.0, 0.5), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(0.0, 1.0, 0.0, 0.25)), f32x4(0.3333, 1.0, 1.0, 0.25), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(0.0, 0.0, 1.0, 1.0)), f32x4(0.6666, 1.0, 1.0, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(0.0, 0.0, 0.0, 1.0)), f32x4(0.0, 0.0, 0.0, 1.0), 0.0001);
|
||
|
try expectVecApproxEqAbs(rgbToHsv(f32x4(1.0, 1.0, 1.0, 1.0)), f32x4(0.0, 0.0, 1.0, 1.0), 0.0001);
|
||
|
}
|
||
|
|
||
|
pub fn hsvToRgb(hsv: F32x4) F32x4 {
|
||
|
const h = swizzle(hsv, .x, .x, .x, .x);
|
||
|
const s = swizzle(hsv, .y, .y, .y, .y);
|
||
|
const v = swizzle(hsv, .z, .z, .z, .z);
|
||
|
|
||
|
const h6 = h * f32x4s(6.0);
|
||
|
const i = floor(h6);
|
||
|
const f = h6 - i;
|
||
|
|
||
|
const p = v * (f32x4s(1.0) - s);
|
||
|
const q = v * (f32x4s(1.0) - f * s);
|
||
|
const t = v * (f32x4s(1.0) - (f32x4s(1.0) - f) * s);
|
||
|
|
||
|
const ii = @as(i32, @intFromFloat(mod(i, f32x4s(6.0))[0]));
|
||
|
const rgb = switch (ii) {
|
||
|
0 => blk: {
|
||
|
const vt = select(boolx4(true, false, false, false), v, t);
|
||
|
break :blk select(boolx4(true, true, false, false), vt, p);
|
||
|
},
|
||
|
1 => blk: {
|
||
|
const qv = select(boolx4(true, false, false, false), q, v);
|
||
|
break :blk select(boolx4(true, true, false, false), qv, p);
|
||
|
},
|
||
|
2 => blk: {
|
||
|
const pv = select(boolx4(true, false, false, false), p, v);
|
||
|
break :blk select(boolx4(true, true, false, false), pv, t);
|
||
|
},
|
||
|
3 => blk: {
|
||
|
const pq = select(boolx4(true, false, false, false), p, q);
|
||
|
break :blk select(boolx4(true, true, false, false), pq, v);
|
||
|
},
|
||
|
4 => blk: {
|
||
|
const tp = select(boolx4(true, false, false, false), t, p);
|
||
|
break :blk select(boolx4(true, true, false, false), tp, v);
|
||
|
},
|
||
|
5 => blk: {
|
||
|
const vp = select(boolx4(true, false, false, false), v, p);
|
||
|
break :blk select(boolx4(true, true, false, false), vp, q);
|
||
|
},
|
||
|
else => unreachable,
|
||
|
};
|
||
|
return select(boolx4(true, true, true, false), rgb, hsv);
|
||
|
}
|
||
|
test "zmath.color.hsvToRgb" {
|
||
|
const epsilon = 0.0005;
|
||
|
try expectVecApproxEqAbs(f32x4(0.2, 0.4, 0.8, 1.0), hsvToRgb(f32x4(0.6111, 0.75, 0.8, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(0.4, 0.2, 0.8, 1.0), hsvToRgb(f32x4(0.7222, 0.75, 0.8, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(0.4, 0.8, 0.2, 1.0), hsvToRgb(f32x4(0.2777, 0.75, 0.8, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(1.0, 0.0, 0.0, 0.5), hsvToRgb(f32x4(0.0, 1.0, 1.0, 0.5)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(0.0, 1.0, 0.0, 0.25), hsvToRgb(f32x4(0.3333, 1.0, 1.0, 0.25)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(0.0, 0.0, 1.0, 1.0), hsvToRgb(f32x4(0.6666, 1.0, 1.0, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(0.0, 0.0, 0.0, 1.0), hsvToRgb(f32x4(0.0, 0.0, 0.0, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(f32x4(1.0, 1.0, 1.0, 1.0), hsvToRgb(f32x4(0.0, 0.0, 1.0, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(
|
||
|
hsvToRgb(rgbToHsv(f32x4(0.1839, 0.632, 0.82198, 1.0))),
|
||
|
f32x4(0.1839, 0.632, 0.82198, 1.0),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
hsvToRgb(rgbToHsv(f32x4(0.82198, 0.1839, 0.632, 1.0))),
|
||
|
f32x4(0.82198, 0.1839, 0.632, 1.0),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
rgbToHsv(hsvToRgb(f32x4(0.82198, 0.1839, 0.632, 1.0))),
|
||
|
f32x4(0.82198, 0.1839, 0.632, 1.0),
|
||
|
epsilon,
|
||
|
);
|
||
|
try expectVecApproxEqAbs(
|
||
|
rgbToHsv(hsvToRgb(f32x4(0.1839, 0.82198, 0.632, 1.0))),
|
||
|
f32x4(0.1839, 0.82198, 0.632, 1.0),
|
||
|
epsilon,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
pub fn rgbToSrgb(rgb: F32x4) F32x4 {
|
||
|
const static = struct {
|
||
|
const cutoff = f32x4(0.0031308, 0.0031308, 0.0031308, 1.0);
|
||
|
const linear = f32x4(12.92, 12.92, 12.92, 1.0);
|
||
|
const scale = f32x4(1.055, 1.055, 1.055, 1.0);
|
||
|
const bias = f32x4(0.055, 0.055, 0.055, 1.0);
|
||
|
const rgamma = 1.0 / 2.4;
|
||
|
};
|
||
|
var v = saturate(rgb);
|
||
|
const v0 = v * static.linear;
|
||
|
const v1 = static.scale * f32x4(
|
||
|
math.pow(f32, v[0], static.rgamma),
|
||
|
math.pow(f32, v[1], static.rgamma),
|
||
|
math.pow(f32, v[2], static.rgamma),
|
||
|
v[3],
|
||
|
) - static.bias;
|
||
|
v = select(v < static.cutoff, v0, v1);
|
||
|
return select(boolx4(true, true, true, false), v, rgb);
|
||
|
}
|
||
|
test "zmath.color.rgbToSrgb" {
|
||
|
const epsilon = 0.001;
|
||
|
try expectVecApproxEqAbs(rgbToSrgb(f32x4(0.2, 0.4, 0.8, 1.0)), f32x4(0.484, 0.665, 0.906, 1.0), epsilon);
|
||
|
}
|
||
|
|
||
|
pub fn srgbToRgb(srgb: F32x4) F32x4 {
|
||
|
const static = struct {
|
||
|
const cutoff = f32x4(0.04045, 0.04045, 0.04045, 1.0);
|
||
|
const rlinear = f32x4(1.0 / 12.92, 1.0 / 12.92, 1.0 / 12.92, 1.0);
|
||
|
const scale = f32x4(1.0 / 1.055, 1.0 / 1.055, 1.0 / 1.055, 1.0);
|
||
|
const bias = f32x4(0.055, 0.055, 0.055, 1.0);
|
||
|
const gamma = 2.4;
|
||
|
};
|
||
|
var v = saturate(srgb);
|
||
|
const v0 = v * static.rlinear;
|
||
|
var v1 = static.scale * (v + static.bias);
|
||
|
v1 = f32x4(
|
||
|
math.pow(f32, v1[0], static.gamma),
|
||
|
math.pow(f32, v1[1], static.gamma),
|
||
|
math.pow(f32, v1[2], static.gamma),
|
||
|
v1[3],
|
||
|
);
|
||
|
v = select(v > static.cutoff, v1, v0);
|
||
|
return select(boolx4(true, true, true, false), v, srgb);
|
||
|
}
|
||
|
test "zmath.color.srgbToRgb" {
|
||
|
const epsilon = 0.0007;
|
||
|
try expectVecApproxEqAbs(f32x4(0.2, 0.4, 0.8, 1.0), srgbToRgb(f32x4(0.484, 0.665, 0.906, 1.0)), epsilon);
|
||
|
try expectVecApproxEqAbs(
|
||
|
rgbToSrgb(srgbToRgb(f32x4(0.1839, 0.82198, 0.632, 1.0))),
|
||
|
f32x4(0.1839, 0.82198, 0.632, 1.0),
|
||
|
epsilon,
|
||
|
);
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// X. Misc functions
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
pub fn linePointDistance(linept0: Vec, linept1: Vec, pt: Vec) F32x4 {
|
||
|
const ptvec = pt - linept0;
|
||
|
const linevec = linept1 - linept0;
|
||
|
const scale = dot3(ptvec, linevec) / lengthSq3(linevec);
|
||
|
return length3(ptvec - linevec * scale);
|
||
|
}
|
||
|
test "zmath.linePointDistance" {
|
||
|
{
|
||
|
const linept0 = f32x4(-1.0, -2.0, -3.0, 1.0);
|
||
|
const linept1 = f32x4(1.0, 2.0, 3.0, 1.0);
|
||
|
const pt = f32x4(1.0, 1.0, 1.0, 1.0);
|
||
|
const v = linePointDistance(linept0, linept1, pt);
|
||
|
try expectVecApproxEqAbs(v, splat(F32x4, 0.654), 0.001);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn sin32(v: f32) f32 {
|
||
|
var y = v - math.tau * @round(v * 1.0 / math.tau);
|
||
|
|
||
|
if (y > 0.5 * math.pi) {
|
||
|
y = math.pi - y;
|
||
|
} else if (y < -math.pi * 0.5) {
|
||
|
y = -math.pi - y;
|
||
|
}
|
||
|
const y2 = y * y;
|
||
|
|
||
|
// 11-degree minimax approximation
|
||
|
var sinv = mulAdd(@as(f32, -2.3889859e-08), y2, 2.7525562e-06);
|
||
|
sinv = mulAdd(sinv, y2, -0.00019840874);
|
||
|
sinv = mulAdd(sinv, y2, 0.0083333310);
|
||
|
sinv = mulAdd(sinv, y2, -0.16666667);
|
||
|
return y * mulAdd(sinv, y2, 1.0);
|
||
|
}
|
||
|
fn cos32(v: f32) f32 {
|
||
|
var y = v - math.tau * @round(v * 1.0 / math.tau);
|
||
|
|
||
|
const sign = blk: {
|
||
|
if (y > 0.5 * math.pi) {
|
||
|
y = math.pi - y;
|
||
|
break :blk @as(f32, -1.0);
|
||
|
} else if (y < -math.pi * 0.5) {
|
||
|
y = -math.pi - y;
|
||
|
break :blk @as(f32, -1.0);
|
||
|
} else {
|
||
|
break :blk @as(f32, 1.0);
|
||
|
}
|
||
|
};
|
||
|
const y2 = y * y;
|
||
|
|
||
|
// 10-degree minimax approximation
|
||
|
var cosv = mulAdd(@as(f32, -2.6051615e-07), y2, 2.4760495e-05);
|
||
|
cosv = mulAdd(cosv, y2, -0.0013888378);
|
||
|
cosv = mulAdd(cosv, y2, 0.041666638);
|
||
|
cosv = mulAdd(cosv, y2, -0.5);
|
||
|
return sign * mulAdd(cosv, y2, 1.0);
|
||
|
}
|
||
|
fn sincos32(v: f32) [2]f32 {
|
||
|
var y = v - math.tau * @round(v * 1.0 / math.tau);
|
||
|
|
||
|
const sign = blk: {
|
||
|
if (y > 0.5 * math.pi) {
|
||
|
y = math.pi - y;
|
||
|
break :blk @as(f32, -1.0);
|
||
|
} else if (y < -math.pi * 0.5) {
|
||
|
y = -math.pi - y;
|
||
|
break :blk @as(f32, -1.0);
|
||
|
} else {
|
||
|
break :blk @as(f32, 1.0);
|
||
|
}
|
||
|
};
|
||
|
const y2 = y * y;
|
||
|
|
||
|
// 11-degree minimax approximation
|
||
|
var sinv = mulAdd(@as(f32, -2.3889859e-08), y2, 2.7525562e-06);
|
||
|
sinv = mulAdd(sinv, y2, -0.00019840874);
|
||
|
sinv = mulAdd(sinv, y2, 0.0083333310);
|
||
|
sinv = mulAdd(sinv, y2, -0.16666667);
|
||
|
sinv = y * mulAdd(sinv, y2, 1.0);
|
||
|
|
||
|
// 10-degree minimax approximation
|
||
|
var cosv = mulAdd(@as(f32, -2.6051615e-07), y2, 2.4760495e-05);
|
||
|
cosv = mulAdd(cosv, y2, -0.0013888378);
|
||
|
cosv = mulAdd(cosv, y2, 0.041666638);
|
||
|
cosv = mulAdd(cosv, y2, -0.5);
|
||
|
cosv = sign * mulAdd(cosv, y2, 1.0);
|
||
|
|
||
|
return .{ sinv, cosv };
|
||
|
}
|
||
|
test "zmath.sincos32" {
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
try expect(math.isNan(sincos32(math.inf(f32))[0]));
|
||
|
try expect(math.isNan(sincos32(math.inf(f32))[1]));
|
||
|
try expect(math.isNan(sincos32(-math.inf(f32))[0]));
|
||
|
try expect(math.isNan(sincos32(-math.inf(f32))[1]));
|
||
|
try expect(math.isNan(sincos32(math.nan(f32))[0]));
|
||
|
try expect(math.isNan(sincos32(-math.nan(f32))[1]));
|
||
|
|
||
|
try expect(math.isNan(sin32(math.inf(f32))));
|
||
|
try expect(math.isNan(cos32(math.inf(f32))));
|
||
|
try expect(math.isNan(sin32(-math.inf(f32))));
|
||
|
try expect(math.isNan(cos32(-math.inf(f32))));
|
||
|
try expect(math.isNan(sin32(math.nan(f32))));
|
||
|
try expect(math.isNan(cos32(-math.nan(f32))));
|
||
|
|
||
|
var f: f32 = -100.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 100) : (i += 1) {
|
||
|
const sc = sincos32(f);
|
||
|
const s0 = sin32(f);
|
||
|
const c0 = cos32(f);
|
||
|
const s = @sin(f);
|
||
|
const c = @cos(f);
|
||
|
try expect(math.approxEqAbs(f32, sc[0], s, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, sc[1], c, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, s0, s, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, c0, c, epsilon));
|
||
|
f += 0.12345 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn asin32(v: f32) f32 {
|
||
|
const x = @abs(v);
|
||
|
var omx = 1.0 - x;
|
||
|
if (omx < 0.0) {
|
||
|
omx = 0.0;
|
||
|
}
|
||
|
const root = @sqrt(omx);
|
||
|
|
||
|
// 7-degree minimax approximation
|
||
|
var result = mulAdd(@as(f32, -0.0012624911), x, 0.0066700901);
|
||
|
result = mulAdd(result, x, -0.0170881256);
|
||
|
result = mulAdd(result, x, 0.0308918810);
|
||
|
result = mulAdd(result, x, -0.0501743046);
|
||
|
result = mulAdd(result, x, 0.0889789874);
|
||
|
result = mulAdd(result, x, -0.2145988016);
|
||
|
result = root * mulAdd(result, x, 1.5707963050);
|
||
|
|
||
|
return if (v >= 0.0) 0.5 * math.pi - result else result - 0.5 * math.pi;
|
||
|
}
|
||
|
test "zmath.asin32" {
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
try expect(math.approxEqAbs(f32, asin(@as(f32, -1.1)), -0.5 * math.pi, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, asin(@as(f32, 1.1)), 0.5 * math.pi, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, asin(@as(f32, -1000.1)), -0.5 * math.pi, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, asin(@as(f32, 100000.1)), 0.5 * math.pi, epsilon));
|
||
|
try expect(math.isNan(asin(math.inf(f32))));
|
||
|
try expect(math.isNan(asin(-math.inf(f32))));
|
||
|
try expect(math.isNan(asin(math.nan(f32))));
|
||
|
try expect(math.isNan(asin(-math.nan(f32))));
|
||
|
|
||
|
try expectVecApproxEqAbs(asin(splat(F32x8, -100.0)), splat(F32x8, -0.5 * math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(asin(splat(F32x16, 100.0)), splat(F32x16, 0.5 * math.pi), epsilon);
|
||
|
try expect(all(isNan(asin(splat(F32x4, math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(asin(splat(F32x4, -math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(asin(splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(asin(splat(F32x4, math.snan(f32)))), 0) == true);
|
||
|
|
||
|
var f: f32 = -1.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 8) : (i += 1) {
|
||
|
const r0 = asin32(f);
|
||
|
const r1 = math.asin(f);
|
||
|
const r4 = asin(splat(F32x4, f));
|
||
|
const r8 = asin(splat(F32x8, f));
|
||
|
const r16 = asin(splat(F32x16, f));
|
||
|
try expect(math.approxEqAbs(f32, r0, r1, epsilon));
|
||
|
try expectVecApproxEqAbs(r4, splat(F32x4, r1), epsilon);
|
||
|
try expectVecApproxEqAbs(r8, splat(F32x8, r1), epsilon);
|
||
|
try expectVecApproxEqAbs(r16, splat(F32x16, r1), epsilon);
|
||
|
f += 0.09 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn acos32(v: f32) f32 {
|
||
|
const x = @abs(v);
|
||
|
var omx = 1.0 - x;
|
||
|
if (omx < 0.0) {
|
||
|
omx = 0.0;
|
||
|
}
|
||
|
const root = @sqrt(omx);
|
||
|
|
||
|
// 7-degree minimax approximation
|
||
|
var result = mulAdd(@as(f32, -0.0012624911), x, 0.0066700901);
|
||
|
result = mulAdd(result, x, -0.0170881256);
|
||
|
result = mulAdd(result, x, 0.0308918810);
|
||
|
result = mulAdd(result, x, -0.0501743046);
|
||
|
result = mulAdd(result, x, 0.0889789874);
|
||
|
result = mulAdd(result, x, -0.2145988016);
|
||
|
result = root * mulAdd(result, x, 1.5707963050);
|
||
|
|
||
|
return if (v >= 0.0) result else math.pi - result;
|
||
|
}
|
||
|
test "zmath.acos32" {
|
||
|
const epsilon = 0.1;
|
||
|
|
||
|
try expect(math.approxEqAbs(f32, acos(@as(f32, -1.1)), math.pi, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, acos(@as(f32, -10000.1)), math.pi, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, acos(@as(f32, 1.1)), 0.0, epsilon));
|
||
|
try expect(math.approxEqAbs(f32, acos(@as(f32, 1000.1)), 0.0, epsilon));
|
||
|
try expect(math.isNan(acos(math.inf(f32))));
|
||
|
try expect(math.isNan(acos(-math.inf(f32))));
|
||
|
try expect(math.isNan(acos(math.nan(f32))));
|
||
|
try expect(math.isNan(acos(-math.nan(f32))));
|
||
|
|
||
|
try expectVecApproxEqAbs(acos(splat(F32x8, -100.0)), splat(F32x8, math.pi), epsilon);
|
||
|
try expectVecApproxEqAbs(acos(splat(F32x16, 100.0)), splat(F32x16, 0.0), epsilon);
|
||
|
try expect(all(isNan(acos(splat(F32x4, math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(acos(splat(F32x4, -math.inf(f32)))), 0) == true);
|
||
|
try expect(all(isNan(acos(splat(F32x4, math.nan(f32)))), 0) == true);
|
||
|
try expect(all(isNan(acos(splat(F32x4, math.snan(f32)))), 0) == true);
|
||
|
|
||
|
var f: f32 = -1.0;
|
||
|
var i: u32 = 0;
|
||
|
while (i < 8) : (i += 1) {
|
||
|
const r0 = acos32(f);
|
||
|
const r1 = math.acos(f);
|
||
|
const r4 = acos(splat(F32x4, f));
|
||
|
const r8 = acos(splat(F32x8, f));
|
||
|
const r16 = acos(splat(F32x16, f));
|
||
|
try expect(math.approxEqAbs(f32, r0, r1, epsilon));
|
||
|
try expectVecApproxEqAbs(r4, splat(F32x4, r1), epsilon);
|
||
|
try expectVecApproxEqAbs(r8, splat(F32x8, r1), epsilon);
|
||
|
try expectVecApproxEqAbs(r16, splat(F32x16, r1), epsilon);
|
||
|
f += 0.09 * @as(f32, @floatFromInt(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn modAngle32(in_angle: f32) f32 {
|
||
|
const angle = in_angle + math.pi;
|
||
|
var temp: f32 = @abs(angle);
|
||
|
temp = temp - (2.0 * math.pi * @as(f32, @floatFromInt(@as(i32, @intFromFloat(temp / math.pi)))));
|
||
|
temp = temp - math.pi;
|
||
|
if (angle < 0.0) {
|
||
|
temp = -temp;
|
||
|
}
|
||
|
return temp;
|
||
|
}
|
||
|
|
||
|
pub fn cmulSoa(re0: anytype, im0: anytype, re1: anytype, im1: anytype) [2]@TypeOf(re0, im0, re1, im1) {
|
||
|
const re0_re1 = re0 * re1;
|
||
|
const re0_im1 = re0 * im1;
|
||
|
return .{
|
||
|
mulAdd(-im0, im1, re0_re1), // re
|
||
|
mulAdd(re1, im0, re0_im1), // im
|
||
|
};
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// FFT (implementation based on xdsp.h from DirectXMath)
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
fn fftButterflyDit4_1(re0: *F32x4, im0: *F32x4) void {
|
||
|
const re0l = swizzle(re0.*, .x, .x, .y, .y);
|
||
|
const re0h = swizzle(re0.*, .z, .z, .w, .w);
|
||
|
|
||
|
const im0l = swizzle(im0.*, .x, .x, .y, .y);
|
||
|
const im0h = swizzle(im0.*, .z, .z, .w, .w);
|
||
|
|
||
|
const re_temp = mulAdd(re0h, f32x4(1.0, -1.0, 1.0, -1.0), re0l);
|
||
|
const im_temp = mulAdd(im0h, f32x4(1.0, -1.0, 1.0, -1.0), im0l);
|
||
|
|
||
|
const re_shuf0 = @shuffle(f32, re_temp, im_temp, [4]i32{ 2, 3, ~@as(i32, 2), ~@as(i32, 3) });
|
||
|
const re_shuf = swizzle(re_shuf0, .x, .w, .x, .w);
|
||
|
const im_shuf = swizzle(re_shuf0, .z, .y, .z, .y);
|
||
|
|
||
|
const re_templ = swizzle(re_temp, .x, .y, .x, .y);
|
||
|
const im_templ = swizzle(im_temp, .x, .y, .x, .y);
|
||
|
|
||
|
re0.* = mulAdd(re_shuf, f32x4(1.0, 1.0, -1.0, -1.0), re_templ);
|
||
|
im0.* = mulAdd(im_shuf, f32x4(1.0, -1.0, -1.0, 1.0), im_templ);
|
||
|
}
|
||
|
|
||
|
fn fftButterflyDit4_4(
|
||
|
re0: *F32x4,
|
||
|
re1: *F32x4,
|
||
|
re2: *F32x4,
|
||
|
re3: *F32x4,
|
||
|
im0: *F32x4,
|
||
|
im1: *F32x4,
|
||
|
im2: *F32x4,
|
||
|
im3: *F32x4,
|
||
|
unity_table_re: []const F32x4,
|
||
|
unity_table_im: []const F32x4,
|
||
|
stride: u32,
|
||
|
last: bool,
|
||
|
) void {
|
||
|
const re_temp0 = re0.* + re2.*;
|
||
|
const im_temp0 = im0.* + im2.*;
|
||
|
|
||
|
const re_temp2 = re1.* + re3.*;
|
||
|
const im_temp2 = im1.* + im3.*;
|
||
|
|
||
|
const re_temp1 = re0.* - re2.*;
|
||
|
const im_temp1 = im0.* - im2.*;
|
||
|
|
||
|
const re_temp3 = re1.* - re3.*;
|
||
|
const im_temp3 = im1.* - im3.*;
|
||
|
|
||
|
var re_temp4 = re_temp0 + re_temp2;
|
||
|
var im_temp4 = im_temp0 + im_temp2;
|
||
|
|
||
|
var re_temp5 = re_temp1 + im_temp3;
|
||
|
var im_temp5 = im_temp1 - re_temp3;
|
||
|
|
||
|
var re_temp6 = re_temp0 - re_temp2;
|
||
|
var im_temp6 = im_temp0 - im_temp2;
|
||
|
|
||
|
var re_temp7 = re_temp1 - im_temp3;
|
||
|
var im_temp7 = im_temp1 + re_temp3;
|
||
|
|
||
|
{
|
||
|
const re_im = cmulSoa(re_temp5, im_temp5, unity_table_re[stride], unity_table_im[stride]);
|
||
|
re_temp5 = re_im[0];
|
||
|
im_temp5 = re_im[1];
|
||
|
}
|
||
|
{
|
||
|
const re_im = cmulSoa(re_temp6, im_temp6, unity_table_re[stride * 2], unity_table_im[stride * 2]);
|
||
|
re_temp6 = re_im[0];
|
||
|
im_temp6 = re_im[1];
|
||
|
}
|
||
|
{
|
||
|
const re_im = cmulSoa(re_temp7, im_temp7, unity_table_re[stride * 3], unity_table_im[stride * 3]);
|
||
|
re_temp7 = re_im[0];
|
||
|
im_temp7 = re_im[1];
|
||
|
}
|
||
|
|
||
|
if (last) {
|
||
|
fftButterflyDit4_1(&re_temp4, &im_temp4);
|
||
|
fftButterflyDit4_1(&re_temp5, &im_temp5);
|
||
|
fftButterflyDit4_1(&re_temp6, &im_temp6);
|
||
|
fftButterflyDit4_1(&re_temp7, &im_temp7);
|
||
|
}
|
||
|
|
||
|
re0.* = re_temp4;
|
||
|
im0.* = im_temp4;
|
||
|
|
||
|
re1.* = re_temp5;
|
||
|
im1.* = im_temp5;
|
||
|
|
||
|
re2.* = re_temp6;
|
||
|
im2.* = im_temp6;
|
||
|
|
||
|
re3.* = re_temp7;
|
||
|
im3.* = im_temp7;
|
||
|
}
|
||
|
|
||
|
fn fft4(re: []F32x4, im: []F32x4, count: u32) void {
|
||
|
assert(std.math.isPowerOfTwo(count));
|
||
|
assert(re.len >= count);
|
||
|
assert(im.len >= count);
|
||
|
|
||
|
var index: u32 = 0;
|
||
|
while (index < count) : (index += 1) {
|
||
|
fftButterflyDit4_1(&re[index], &im[index]);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.fft4" {
|
||
|
const epsilon = 0.0001;
|
||
|
var re = [_]F32x4{f32x4(1.0, 2.0, 3.0, 4.0)};
|
||
|
var im = [_]F32x4{f32x4s(0.0)};
|
||
|
fft4(re[0..], im[0..], 1);
|
||
|
|
||
|
var re_uns: [1]F32x4 = undefined;
|
||
|
var im_uns: [1]F32x4 = undefined;
|
||
|
fftUnswizzle(re[0..], re_uns[0..]);
|
||
|
fftUnswizzle(im[0..], im_uns[0..]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re_uns[0], f32x4(10.0, -2.0, -2.0, -2.0), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[0], f32x4(0.0, 2.0, 0.0, -2.0), epsilon);
|
||
|
}
|
||
|
|
||
|
fn fft8(re: []F32x4, im: []F32x4, count: u32) void {
|
||
|
assert(std.math.isPowerOfTwo(count));
|
||
|
assert(re.len >= 2 * count);
|
||
|
assert(im.len >= 2 * count);
|
||
|
|
||
|
var index: u32 = 0;
|
||
|
while (index < count) : (index += 1) {
|
||
|
var pre = re[index * 2 ..];
|
||
|
var pim = im[index * 2 ..];
|
||
|
|
||
|
var odds_re = @shuffle(f32, pre[0], pre[1], [4]i32{ 1, 3, ~@as(i32, 1), ~@as(i32, 3) });
|
||
|
var evens_re = @shuffle(f32, pre[0], pre[1], [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) });
|
||
|
var odds_im = @shuffle(f32, pim[0], pim[1], [4]i32{ 1, 3, ~@as(i32, 1), ~@as(i32, 3) });
|
||
|
var evens_im = @shuffle(f32, pim[0], pim[1], [4]i32{ 0, 2, ~@as(i32, 0), ~@as(i32, 2) });
|
||
|
fftButterflyDit4_1(&odds_re, &odds_im);
|
||
|
fftButterflyDit4_1(&evens_re, &evens_im);
|
||
|
|
||
|
{
|
||
|
const re_im = cmulSoa(
|
||
|
odds_re,
|
||
|
odds_im,
|
||
|
f32x4(1.0, 0.70710677, 0.0, -0.70710677),
|
||
|
f32x4(0.0, -0.70710677, -1.0, -0.70710677),
|
||
|
);
|
||
|
pre[0] = evens_re + re_im[0];
|
||
|
pim[0] = evens_im + re_im[1];
|
||
|
}
|
||
|
{
|
||
|
const re_im = cmulSoa(
|
||
|
odds_re,
|
||
|
odds_im,
|
||
|
f32x4(-1.0, -0.70710677, 0.0, 0.70710677),
|
||
|
f32x4(0.0, 0.70710677, 1.0, 0.70710677),
|
||
|
);
|
||
|
pre[1] = evens_re + re_im[0];
|
||
|
pim[1] = evens_im + re_im[1];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
test "zmath.fft8" {
|
||
|
const epsilon = 0.0001;
|
||
|
var re = [_]F32x4{ f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0) };
|
||
|
var im = [_]F32x4{ f32x4s(0.0), f32x4s(0.0) };
|
||
|
fft8(re[0..], im[0..], 1);
|
||
|
|
||
|
var re_uns: [2]F32x4 = undefined;
|
||
|
var im_uns: [2]F32x4 = undefined;
|
||
|
fftUnswizzle(re[0..], re_uns[0..]);
|
||
|
fftUnswizzle(im[0..], im_uns[0..]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re_uns[0], f32x4(36.0, -4.0, -4.0, -4.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re_uns[1], f32x4(-4.0, -4.0, -4.0, -4.0), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[0], f32x4(0.0, 9.656854, 4.0, 1.656854), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[1], f32x4(0.0, -1.656854, -4.0, -9.656854), epsilon);
|
||
|
}
|
||
|
|
||
|
fn fft16(re: []F32x4, im: []F32x4, count: u32) void {
|
||
|
assert(std.math.isPowerOfTwo(count));
|
||
|
assert(re.len >= 4 * count);
|
||
|
assert(im.len >= 4 * count);
|
||
|
|
||
|
const static = struct {
|
||
|
const unity_table_re = [4]F32x4{
|
||
|
f32x4(1.0, 1.0, 1.0, 1.0),
|
||
|
f32x4(1.0, 0.92387950, 0.70710677, 0.38268343),
|
||
|
f32x4(1.0, 0.70710677, -4.3711388e-008, -0.70710677),
|
||
|
f32x4(1.0, 0.38268343, -0.70710677, -0.92387950),
|
||
|
};
|
||
|
const unity_table_im = [4]F32x4{
|
||
|
f32x4(-0.0, -0.0, -0.0, -0.0),
|
||
|
f32x4(-0.0, -0.38268343, -0.70710677, -0.92387950),
|
||
|
f32x4(-0.0, -0.70710677, -1.0, -0.70710677),
|
||
|
f32x4(-0.0, -0.92387950, -0.70710677, 0.38268343),
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var index: u32 = 0;
|
||
|
while (index < count) : (index += 1) {
|
||
|
fftButterflyDit4_4(
|
||
|
&re[index * 4],
|
||
|
&re[index * 4 + 1],
|
||
|
&re[index * 4 + 2],
|
||
|
&re[index * 4 + 3],
|
||
|
&im[index * 4],
|
||
|
&im[index * 4 + 1],
|
||
|
&im[index * 4 + 2],
|
||
|
&im[index * 4 + 3],
|
||
|
static.unity_table_re[0..],
|
||
|
static.unity_table_im[0..],
|
||
|
1,
|
||
|
true,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.fft16" {
|
||
|
const epsilon = 0.0001;
|
||
|
var re = [_]F32x4{
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0),
|
||
|
f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0),
|
||
|
f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
};
|
||
|
var im = [_]F32x4{ f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0) };
|
||
|
fft16(re[0..], im[0..], 1);
|
||
|
|
||
|
var re_uns: [4]F32x4 = undefined;
|
||
|
var im_uns: [4]F32x4 = undefined;
|
||
|
fftUnswizzle(re[0..], re_uns[0..]);
|
||
|
fftUnswizzle(im[0..], im_uns[0..]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re_uns[0], f32x4(136.0, -8.0, -8.0, -8.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re_uns[1], f32x4(-8.0, -8.0, -8.0, -8.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re_uns[2], f32x4(-8.0, -8.0, -8.0, -8.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re_uns[3], f32x4(-8.0, -8.0, -8.0, -8.0), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[0], f32x4(0.0, 40.218716, 19.313708, 11.972846), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[1], f32x4(8.0, 5.345429, 3.313708, 1.591299), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[2], f32x4(0.0, -1.591299, -3.313708, -5.345429), epsilon);
|
||
|
try expectVecApproxEqAbs(im_uns[3], f32x4(-8.0, -11.972846, -19.313708, -40.218716), epsilon);
|
||
|
}
|
||
|
|
||
|
fn fftN(re: []F32x4, im: []F32x4, unity_table: []const F32x4, length: u32, count: u32) void {
|
||
|
assert(length > 16);
|
||
|
assert(std.math.isPowerOfTwo(length));
|
||
|
assert(std.math.isPowerOfTwo(count));
|
||
|
assert(re.len >= length * count / 4);
|
||
|
assert(re.len == im.len);
|
||
|
|
||
|
const total = count * length;
|
||
|
const total_vectors = total / 4;
|
||
|
const stage_vectors = length / 4;
|
||
|
const stage_vectors_mask = stage_vectors - 1;
|
||
|
const stride = length / 16;
|
||
|
const stride_mask = stride - 1;
|
||
|
const stride_inv_mask = ~stride_mask;
|
||
|
|
||
|
var unity_table_re = unity_table;
|
||
|
var unity_table_im = unity_table[length / 4 ..];
|
||
|
|
||
|
var index: u32 = 0;
|
||
|
while (index < total_vectors / 4) : (index += 1) {
|
||
|
const n = (index & stride_inv_mask) * 4 + (index & stride_mask);
|
||
|
fftButterflyDit4_4(
|
||
|
&re[n],
|
||
|
&re[n + stride],
|
||
|
&re[n + stride * 2],
|
||
|
&re[n + stride * 3],
|
||
|
&im[n],
|
||
|
&im[n + stride],
|
||
|
&im[n + stride * 2],
|
||
|
&im[n + stride * 3],
|
||
|
unity_table_re[(n & stage_vectors_mask)..],
|
||
|
unity_table_im[(n & stage_vectors_mask)..],
|
||
|
stride,
|
||
|
false,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (length > 16 * 4) {
|
||
|
fftN(re, im, unity_table[(length / 2)..], length / 4, count * 4);
|
||
|
} else if (length == 16 * 4) {
|
||
|
fft16(re, im, count * 4);
|
||
|
} else if (length == 8 * 4) {
|
||
|
fft8(re, im, count * 4);
|
||
|
} else if (length == 4 * 4) {
|
||
|
fft4(re, im, count * 4);
|
||
|
}
|
||
|
}
|
||
|
test "zmath.fftN" {
|
||
|
var unity_table: [128]F32x4 = undefined;
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
// 32 samples
|
||
|
{
|
||
|
var re = [_]F32x4{
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
};
|
||
|
var im = [_]F32x4{
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
};
|
||
|
|
||
|
fftInitUnityTable(unity_table[0..32]);
|
||
|
fft(re[0..], im[0..], unity_table[0..32]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re[0], f32x4(528.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[1], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[2], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[3], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[4], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[5], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[6], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[7], f32x4(-16.0, -16.0, -16.0, -16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(im[0], f32x4(0.0, 162.450726, 80.437432, 52.744931), epsilon);
|
||
|
try expectVecApproxEqAbs(im[1], f32x4(38.627417, 29.933895, 23.945692, 19.496056), epsilon);
|
||
|
try expectVecApproxEqAbs(im[2], f32x4(16.0, 13.130861, 10.690858, 8.552178), epsilon);
|
||
|
try expectVecApproxEqAbs(im[3], f32x4(6.627417, 4.853547, 3.182598, 1.575862), epsilon);
|
||
|
try expectVecApproxEqAbs(im[4], f32x4(0.0, -1.575862, -3.182598, -4.853547), epsilon);
|
||
|
try expectVecApproxEqAbs(im[5], f32x4(-6.627417, -8.552178, -10.690858, -13.130861), epsilon);
|
||
|
try expectVecApproxEqAbs(im[6], f32x4(-16.0, -19.496056, -23.945692, -29.933895), epsilon);
|
||
|
try expectVecApproxEqAbs(im[7], f32x4(-38.627417, -52.744931, -80.437432, -162.450726), epsilon);
|
||
|
}
|
||
|
|
||
|
// 64 samples
|
||
|
{
|
||
|
var re = [_]F32x4{
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
};
|
||
|
var im = [_]F32x4{
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
};
|
||
|
|
||
|
fftInitUnityTable(unity_table[0..64]);
|
||
|
fft(re[0..], im[0..], unity_table[0..64]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re[0], f32x4(1056.0, 0.0, -32.0, 0.0), epsilon);
|
||
|
var i: u32 = 1;
|
||
|
while (i < 16) : (i += 1) {
|
||
|
try expectVecApproxEqAbs(re[i], f32x4(-32.0, 0.0, -32.0, 0.0), epsilon);
|
||
|
}
|
||
|
|
||
|
const expected = [_]f32{
|
||
|
0.0, 0.0, 324.901452, 0.000000, 160.874864, 0.0, 105.489863, 0.000000,
|
||
|
77.254834, 0.0, 59.867789, 0.0, 47.891384, 0.0, 38.992113, 0.0,
|
||
|
32.000000, 0.000000, 26.261721, 0.000000, 21.381716, 0.000000, 17.104356, 0.000000,
|
||
|
13.254834, 0.000000, 9.707094, 0.000000, 6.365196, 0.000000, 3.151725, 0.000000,
|
||
|
0.000000, 0.000000, -3.151725, 0.000000, -6.365196, 0.000000, -9.707094, 0.000000,
|
||
|
-13.254834, 0.000000, -17.104356, 0.000000, -21.381716, 0.000000, -26.261721, 0.000000,
|
||
|
-32.000000, 0.000000, -38.992113, 0.000000, -47.891384, 0.000000, -59.867789, 0.000000,
|
||
|
-77.254834, 0.000000, -105.489863, 0.000000, -160.874864, 0.000000, -324.901452, 0.000000,
|
||
|
};
|
||
|
for (expected, 0..) |e, ie| {
|
||
|
try expect(std.math.approxEqAbs(f32, e, im[(ie / 4)][ie % 4], epsilon));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 128 samples
|
||
|
{
|
||
|
var re = [_]F32x4{
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
};
|
||
|
var im = [_]F32x4{
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
};
|
||
|
|
||
|
fftInitUnityTable(unity_table[0..128]);
|
||
|
fft(re[0..], im[0..], unity_table[0..128]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re[0], f32x4(2112.0, 0.0, 0.0, 0.0), epsilon);
|
||
|
var i: u32 = 1;
|
||
|
while (i < 32) : (i += 1) {
|
||
|
try expectVecApproxEqAbs(re[i], f32x4(-64.0, 0.0, 0.0, 0.0), epsilon);
|
||
|
}
|
||
|
|
||
|
const expected = [_]f32{
|
||
|
0.000000, 0.000000, 0.000000, 0.000000, 649.802905, 0.000000, 0.000000, 0.000000,
|
||
|
321.749727, 0.000000, 0.000000, 0.000000, 210.979725, 0.000000, 0.000000, 0.000000,
|
||
|
154.509668, 0.000000, 0.000000, 0.000000, 119.735578, 0.000000, 0.000000, 0.000000,
|
||
|
95.782769, 0.000000, 0.000000, 0.000000, 77.984226, 0.000000, 0.000000, 0.000000,
|
||
|
64.000000, 0.000000, 0.000000, 0.000000, 52.523443, 0.000000, 0.000000, 0.000000,
|
||
|
42.763433, 0.000000, 0.000000, 0.000000, 34.208713, 0.000000, 0.000000, 0.000000,
|
||
|
26.509668, 0.000000, 0.000000, 0.000000, 19.414188, 0.000000, 0.000000, 0.000000,
|
||
|
12.730392, 0.000000, 0.000000, 0.000000, 6.303450, 0.000000, 0.000000, 0.000000,
|
||
|
0.000000, 0.000000, 0.000000, 0.000000, -6.303450, 0.000000, 0.000000, 0.000000,
|
||
|
-12.730392, 0.000000, 0.000000, 0.000000, -19.414188, 0.000000, 0.000000, 0.000000,
|
||
|
-26.509668, 0.000000, 0.000000, 0.000000, -34.208713, 0.000000, 0.000000, 0.000000,
|
||
|
-42.763433, 0.000000, 0.000000, 0.000000, -52.523443, 0.000000, 0.000000, 0.000000,
|
||
|
-64.000000, 0.000000, 0.000000, 0.000000, -77.984226, 0.000000, 0.000000, 0.000000,
|
||
|
-95.782769, 0.000000, 0.000000, 0.000000, -119.735578, 0.000000, 0.000000, 0.000000,
|
||
|
-154.509668, 0.000000, 0.000000, 0.000000, -210.979725, 0.000000, 0.000000, 0.000000,
|
||
|
-321.749727, 0.000000, 0.000000, 0.000000, -649.802905, 0.000000, 0.000000, 0.000000,
|
||
|
};
|
||
|
for (expected, 0..) |e, ie| {
|
||
|
try expect(std.math.approxEqAbs(f32, e, im[(ie / 4)][ie % 4], epsilon));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn fftUnswizzle(input: []const F32x4, output: []F32x4) void {
|
||
|
assert(std.math.isPowerOfTwo(input.len));
|
||
|
assert(input.len == output.len);
|
||
|
assert(input.ptr != output.ptr);
|
||
|
|
||
|
const log2_length = std.math.log2_int(usize, input.len * 4);
|
||
|
assert(log2_length >= 2);
|
||
|
|
||
|
const length = input.len;
|
||
|
|
||
|
const f32_output = @as([*]f32, @ptrCast(output.ptr))[0 .. output.len * 4];
|
||
|
|
||
|
const static = struct {
|
||
|
const swizzle_table = [256]u8{
|
||
|
0x00, 0x40, 0x80, 0xC0, 0x10, 0x50, 0x90, 0xD0, 0x20, 0x60, 0xA0, 0xE0, 0x30, 0x70, 0xB0, 0xF0,
|
||
|
0x04, 0x44, 0x84, 0xC4, 0x14, 0x54, 0x94, 0xD4, 0x24, 0x64, 0xA4, 0xE4, 0x34, 0x74, 0xB4, 0xF4,
|
||
|
0x08, 0x48, 0x88, 0xC8, 0x18, 0x58, 0x98, 0xD8, 0x28, 0x68, 0xA8, 0xE8, 0x38, 0x78, 0xB8, 0xF8,
|
||
|
0x0C, 0x4C, 0x8C, 0xCC, 0x1C, 0x5C, 0x9C, 0xDC, 0x2C, 0x6C, 0xAC, 0xEC, 0x3C, 0x7C, 0xBC, 0xFC,
|
||
|
0x01, 0x41, 0x81, 0xC1, 0x11, 0x51, 0x91, 0xD1, 0x21, 0x61, 0xA1, 0xE1, 0x31, 0x71, 0xB1, 0xF1,
|
||
|
0x05, 0x45, 0x85, 0xC5, 0x15, 0x55, 0x95, 0xD5, 0x25, 0x65, 0xA5, 0xE5, 0x35, 0x75, 0xB5, 0xF5,
|
||
|
0x09, 0x49, 0x89, 0xC9, 0x19, 0x59, 0x99, 0xD9, 0x29, 0x69, 0xA9, 0xE9, 0x39, 0x79, 0xB9, 0xF9,
|
||
|
0x0D, 0x4D, 0x8D, 0xCD, 0x1D, 0x5D, 0x9D, 0xDD, 0x2D, 0x6D, 0xAD, 0xED, 0x3D, 0x7D, 0xBD, 0xFD,
|
||
|
0x02, 0x42, 0x82, 0xC2, 0x12, 0x52, 0x92, 0xD2, 0x22, 0x62, 0xA2, 0xE2, 0x32, 0x72, 0xB2, 0xF2,
|
||
|
0x06, 0x46, 0x86, 0xC6, 0x16, 0x56, 0x96, 0xD6, 0x26, 0x66, 0xA6, 0xE6, 0x36, 0x76, 0xB6, 0xF6,
|
||
|
0x0A, 0x4A, 0x8A, 0xCA, 0x1A, 0x5A, 0x9A, 0xDA, 0x2A, 0x6A, 0xAA, 0xEA, 0x3A, 0x7A, 0xBA, 0xFA,
|
||
|
0x0E, 0x4E, 0x8E, 0xCE, 0x1E, 0x5E, 0x9E, 0xDE, 0x2E, 0x6E, 0xAE, 0xEE, 0x3E, 0x7E, 0xBE, 0xFE,
|
||
|
0x03, 0x43, 0x83, 0xC3, 0x13, 0x53, 0x93, 0xD3, 0x23, 0x63, 0xA3, 0xE3, 0x33, 0x73, 0xB3, 0xF3,
|
||
|
0x07, 0x47, 0x87, 0xC7, 0x17, 0x57, 0x97, 0xD7, 0x27, 0x67, 0xA7, 0xE7, 0x37, 0x77, 0xB7, 0xF7,
|
||
|
0x0B, 0x4B, 0x8B, 0xCB, 0x1B, 0x5B, 0x9B, 0xDB, 0x2B, 0x6B, 0xAB, 0xEB, 0x3B, 0x7B, 0xBB, 0xFB,
|
||
|
0x0F, 0x4F, 0x8F, 0xCF, 0x1F, 0x5F, 0x9F, 0xDF, 0x2F, 0x6F, 0xAF, 0xEF, 0x3F, 0x7F, 0xBF, 0xFF,
|
||
|
};
|
||
|
};
|
||
|
|
||
|
if ((log2_length & 1) == 0) {
|
||
|
const rev32 = @as(u6, @intCast(32 - log2_length));
|
||
|
var index: usize = 0;
|
||
|
while (index < length) : (index += 1) {
|
||
|
const n = index * 4;
|
||
|
const addr =
|
||
|
(@as(usize, @intCast(static.swizzle_table[n & 0xff])) << 24) |
|
||
|
(@as(usize, @intCast(static.swizzle_table[(n >> 8) & 0xff])) << 16) |
|
||
|
(@as(usize, @intCast(static.swizzle_table[(n >> 16) & 0xff])) << 8) |
|
||
|
@as(usize, @intCast(static.swizzle_table[(n >> 24) & 0xff]));
|
||
|
f32_output[addr >> rev32] = input[index][0];
|
||
|
f32_output[(0x40000000 | addr) >> rev32] = input[index][1];
|
||
|
f32_output[(0x80000000 | addr) >> rev32] = input[index][2];
|
||
|
f32_output[(0xC0000000 | addr) >> rev32] = input[index][3];
|
||
|
}
|
||
|
} else {
|
||
|
const rev7 = @as(usize, 1) << @as(u6, @intCast(log2_length - 3));
|
||
|
const rev32 = @as(u6, @intCast(32 - (log2_length - 3)));
|
||
|
var index: usize = 0;
|
||
|
while (index < length) : (index += 1) {
|
||
|
const n = index / 2;
|
||
|
var addr =
|
||
|
(((@as(usize, @intCast(static.swizzle_table[n & 0xff])) << 24) |
|
||
|
(@as(usize, @intCast(static.swizzle_table[(n >> 8) & 0xff])) << 16) |
|
||
|
(@as(usize, @intCast(static.swizzle_table[(n >> 16) & 0xff])) << 8) |
|
||
|
(@as(usize, @intCast(static.swizzle_table[(n >> 24) & 0xff])))) >> rev32) |
|
||
|
((index & 1) * rev7 * 4);
|
||
|
f32_output[addr] = input[index][0];
|
||
|
addr += rev7;
|
||
|
f32_output[addr] = input[index][1];
|
||
|
addr += rev7;
|
||
|
f32_output[addr] = input[index][2];
|
||
|
addr += rev7;
|
||
|
f32_output[addr] = input[index][3];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn fftInitUnityTable(out_unity_table: []F32x4) void {
|
||
|
assert(std.math.isPowerOfTwo(out_unity_table.len));
|
||
|
assert(out_unity_table.len >= 32 and out_unity_table.len <= 512);
|
||
|
|
||
|
var unity_table = out_unity_table;
|
||
|
|
||
|
const v0123 = f32x4(0.0, 1.0, 2.0, 3.0);
|
||
|
var length = out_unity_table.len / 4;
|
||
|
var vlstep = f32x4s(0.5 * math.pi / @as(f32, @floatFromInt(length)));
|
||
|
|
||
|
while (true) {
|
||
|
length /= 4;
|
||
|
var vjp = v0123;
|
||
|
|
||
|
var j: u32 = 0;
|
||
|
while (j < length) : (j += 1) {
|
||
|
unity_table[j] = f32x4s(1.0);
|
||
|
unity_table[j + length * 4] = f32x4s(0.0);
|
||
|
|
||
|
var vls = vjp * vlstep;
|
||
|
var sin_cos = sincos(vls);
|
||
|
unity_table[j + length] = sin_cos[1];
|
||
|
unity_table[j + length * 5] = sin_cos[0] * f32x4s(-1.0);
|
||
|
|
||
|
var vijp = vjp + vjp;
|
||
|
vls = vijp * vlstep;
|
||
|
sin_cos = sincos(vls);
|
||
|
unity_table[j + length * 2] = sin_cos[1];
|
||
|
unity_table[j + length * 6] = sin_cos[0] * f32x4s(-1.0);
|
||
|
|
||
|
vijp = vijp + vjp;
|
||
|
vls = vijp * vlstep;
|
||
|
sin_cos = sincos(vls);
|
||
|
unity_table[j + length * 3] = sin_cos[1];
|
||
|
unity_table[j + length * 7] = sin_cos[0] * f32x4s(-1.0);
|
||
|
|
||
|
vjp += f32x4s(4.0);
|
||
|
}
|
||
|
vlstep *= f32x4s(4.0);
|
||
|
unity_table = unity_table[8 * length ..];
|
||
|
|
||
|
if (length <= 4)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn fft(re: []F32x4, im: []F32x4, unity_table: []const F32x4) void {
|
||
|
const length = @as(u32, @intCast(re.len * 4));
|
||
|
assert(std.math.isPowerOfTwo(length));
|
||
|
assert(length >= 4 and length <= 512);
|
||
|
assert(re.len == im.len);
|
||
|
|
||
|
var re_temp_storage: [128]F32x4 = undefined;
|
||
|
var im_temp_storage: [128]F32x4 = undefined;
|
||
|
const re_temp = re_temp_storage[0..re.len];
|
||
|
const im_temp = im_temp_storage[0..im.len];
|
||
|
|
||
|
@memcpy(re_temp, re);
|
||
|
@memcpy(im_temp, im);
|
||
|
|
||
|
if (length > 16) {
|
||
|
assert(unity_table.len == length);
|
||
|
fftN(re_temp, im_temp, unity_table, length, 1);
|
||
|
} else if (length == 16) {
|
||
|
fft16(re_temp, im_temp, 1);
|
||
|
} else if (length == 8) {
|
||
|
fft8(re_temp, im_temp, 1);
|
||
|
} else if (length == 4) {
|
||
|
fft4(re_temp, im_temp, 1);
|
||
|
}
|
||
|
|
||
|
fftUnswizzle(re_temp, re);
|
||
|
fftUnswizzle(im_temp, im);
|
||
|
}
|
||
|
|
||
|
pub fn ifft(re: []F32x4, im: []const F32x4, unity_table: []const F32x4) void {
|
||
|
const length = @as(u32, @intCast(re.len * 4));
|
||
|
assert(std.math.isPowerOfTwo(length));
|
||
|
assert(length >= 4 and length <= 512);
|
||
|
assert(re.len == im.len);
|
||
|
|
||
|
var re_temp_storage: [128]F32x4 = undefined;
|
||
|
var im_temp_storage: [128]F32x4 = undefined;
|
||
|
var re_temp = re_temp_storage[0..re.len];
|
||
|
var im_temp = im_temp_storage[0..im.len];
|
||
|
|
||
|
const rnp = f32x4s(1.0 / @as(f32, @floatFromInt(length)));
|
||
|
const rnm = f32x4s(-1.0 / @as(f32, @floatFromInt(length)));
|
||
|
|
||
|
for (re, 0..) |_, i| {
|
||
|
re_temp[i] = re[i] * rnp;
|
||
|
im_temp[i] = im[i] * rnm;
|
||
|
}
|
||
|
|
||
|
if (length > 16) {
|
||
|
assert(unity_table.len == length);
|
||
|
fftN(re_temp, im_temp, unity_table, length, 1);
|
||
|
} else if (length == 16) {
|
||
|
fft16(re_temp, im_temp, 1);
|
||
|
} else if (length == 8) {
|
||
|
fft8(re_temp, im_temp, 1);
|
||
|
} else if (length == 4) {
|
||
|
fft4(re_temp, im_temp, 1);
|
||
|
}
|
||
|
|
||
|
fftUnswizzle(re_temp, re);
|
||
|
}
|
||
|
test "zmath.ifft" {
|
||
|
var unity_table: [512]F32x4 = undefined;
|
||
|
const epsilon = 0.0001;
|
||
|
|
||
|
// 64 samples
|
||
|
{
|
||
|
var re = [_]F32x4{
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
f32x4(1.0, 2.0, 3.0, 4.0), f32x4(5.0, 6.0, 7.0, 8.0),
|
||
|
f32x4(9.0, 10.0, 11.0, 12.0), f32x4(13.0, 14.0, 15.0, 16.0),
|
||
|
f32x4(17.0, 18.0, 19.0, 20.0), f32x4(21.0, 22.0, 23.0, 24.0),
|
||
|
f32x4(25.0, 26.0, 27.0, 28.0), f32x4(29.0, 30.0, 31.0, 32.0),
|
||
|
};
|
||
|
var im = [_]F32x4{
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
f32x4s(0.0), f32x4s(0.0), f32x4s(0.0), f32x4s(0.0),
|
||
|
};
|
||
|
|
||
|
fftInitUnityTable(unity_table[0..64]);
|
||
|
fft(re[0..], im[0..], unity_table[0..64]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re[0], f32x4(1056.0, 0.0, -32.0, 0.0), epsilon);
|
||
|
var i: u32 = 1;
|
||
|
while (i < 16) : (i += 1) {
|
||
|
try expectVecApproxEqAbs(re[i], f32x4(-32.0, 0.0, -32.0, 0.0), epsilon);
|
||
|
}
|
||
|
|
||
|
ifft(re[0..], im[0..], unity_table[0..64]);
|
||
|
|
||
|
try expectVecApproxEqAbs(re[0], f32x4(1.0, 2.0, 3.0, 4.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[1], f32x4(5.0, 6.0, 7.0, 8.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[2], f32x4(9.0, 10.0, 11.0, 12.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[3], f32x4(13.0, 14.0, 15.0, 16.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[4], f32x4(17.0, 18.0, 19.0, 20.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[5], f32x4(21.0, 22.0, 23.0, 24.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[6], f32x4(25.0, 26.0, 27.0, 28.0), epsilon);
|
||
|
try expectVecApproxEqAbs(re[7], f32x4(29.0, 30.0, 31.0, 32.0), epsilon);
|
||
|
}
|
||
|
|
||
|
// 512 samples
|
||
|
{
|
||
|
var re: [128]F32x4 = undefined;
|
||
|
var im = [_]F32x4{f32x4s(0.0)} ** 128;
|
||
|
|
||
|
for (&re, 0..) |*v, i| {
|
||
|
const f = @as(f32, @floatFromInt(i * 4));
|
||
|
v.* = f32x4(f + 1.0, f + 2.0, f + 3.0, f + 4.0);
|
||
|
}
|
||
|
|
||
|
fftInitUnityTable(unity_table[0..512]);
|
||
|
fft(re[0..], im[0..], unity_table[0..512]);
|
||
|
|
||
|
for (re, 0..) |v, i| {
|
||
|
const f = @as(f32, @floatFromInt(i * 4));
|
||
|
try expect(!approxEqAbs(v, f32x4(f + 1.0, f + 2.0, f + 3.0, f + 4.0), epsilon));
|
||
|
}
|
||
|
|
||
|
ifft(re[0..], im[0..], unity_table[0..512]);
|
||
|
|
||
|
for (re, 0..) |v, i| {
|
||
|
const f = @as(f32, @floatFromInt(i * 4));
|
||
|
try expectVecApproxEqAbs(v, f32x4(f + 1.0, f + 2.0, f + 3.0, f + 4.0), epsilon);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// ------------------------------------------------------------------------------
|
||
|
//
|
||
|
// Private functions and constants
|
||
|
//
|
||
|
// ------------------------------------------------------------------------------
|
||
|
const f32x4_sign_mask1: F32x4 = F32x4{ @as(f32, @bitCast(@as(u32, 0x8000_0000))), 0, 0, 0 };
|
||
|
const f32x4_mask2: F32x4 = F32x4{
|
||
|
@as(f32, @bitCast(@as(u32, 0xffff_ffff))),
|
||
|
@as(f32, @bitCast(@as(u32, 0xffff_ffff))),
|
||
|
0,
|
||
|
0,
|
||
|
};
|
||
|
const f32x4_mask3: F32x4 = F32x4{
|
||
|
@as(f32, @bitCast(@as(u32, 0xffff_ffff))),
|
||
|
@as(f32, @bitCast(@as(u32, 0xffff_ffff))),
|
||
|
@as(f32, @bitCast(@as(u32, 0xffff_ffff))),
|
||
|
0,
|
||
|
};
|
||
|
|
||
|
inline fn splatNegativeZero(comptime T: type) T {
|
||
|
return @splat(@as(f32, @bitCast(@as(u32, 0x8000_0000))));
|
||
|
}
|
||
|
inline fn splatNoFraction(comptime T: type) T {
|
||
|
return @splat(@as(f32, 8_388_608.0));
|
||
|
}
|
||
|
inline fn splatAbsMask(comptime T: type) T {
|
||
|
return @splat(@as(f32, @bitCast(@as(u32, 0x7fff_ffff))));
|
||
|
}
|
||
|
|
||
|
fn floatToIntAndBack(v: anytype) @TypeOf(v) {
|
||
|
// This routine won't handle nan, inf and numbers greater than 8_388_608.0 (will generate undefined values).
|
||
|
@setRuntimeSafety(false);
|
||
|
|
||
|
const T = @TypeOf(v);
|
||
|
const len = veclen(T);
|
||
|
|
||
|
var vi32: [len]i32 = undefined;
|
||
|
comptime var i: u32 = 0;
|
||
|
// vcvttps2dq
|
||
|
inline while (i < len) : (i += 1) {
|
||
|
vi32[i] = @as(i32, @intFromFloat(v[i]));
|
||
|
}
|
||
|
|
||
|
var vf32: [len]f32 = undefined;
|
||
|
i = 0;
|
||
|
// vcvtdq2ps
|
||
|
inline while (i < len) : (i += 1) {
|
||
|
vf32[i] = @as(f32, @floatFromInt(vi32[i]));
|
||
|
}
|
||
|
|
||
|
return vf32;
|
||
|
}
|
||
|
test "zmath.floatToIntAndBack" {
|
||
|
{
|
||
|
const v = floatToIntAndBack(f32x4(1.1, 2.9, 3.0, -4.5));
|
||
|
try expectVecEqual(v, f32x4(1.0, 2.0, 3.0, -4.0));
|
||
|
}
|
||
|
{
|
||
|
const v = floatToIntAndBack(f32x8(1.1, 2.9, 3.0, -4.5, 2.5, -2.5, 1.1, -100.2));
|
||
|
try expectVecEqual(v, f32x8(1.0, 2.0, 3.0, -4.0, 2.0, -2.0, 1.0, -100.0));
|
||
|
}
|
||
|
{
|
||
|
const v = floatToIntAndBack(f32x4(math.inf(f32), 2.9, math.nan(f32), math.snan(f32)));
|
||
|
try expect(v[1] == 2.0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn expectVecEqual(expected: anytype, actual: anytype) !void {
|
||
|
const T = @TypeOf(expected, actual);
|
||
|
inline for (0..veclen(T)) |i| {
|
||
|
try std.testing.expectEqual(expected[i], actual[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn expectVecApproxEqAbs(expected: anytype, actual: anytype, eps: f32) !void {
|
||
|
const T = @TypeOf(expected, actual);
|
||
|
inline for (0..veclen(T)) |i| {
|
||
|
try std.testing.expectApproxEqAbs(expected[i], actual[i], eps);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn approxEqAbs(v0: anytype, v1: anytype, eps: f32) bool {
|
||
|
const T = @TypeOf(v0, v1);
|
||
|
comptime var i: comptime_int = 0;
|
||
|
inline while (i < veclen(T)) : (i += 1) {
|
||
|
if (!math.approxEqAbs(f32, v0[i], v1[i], eps)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// This software is available under 2 licenses -- choose whichever you prefer.
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// ALTERNATIVE A - MIT License
|
||
|
// Copyright (c) 2022 Michal Ziulek and Contributors
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||
|
// this software and associated documentation files (the "Software"), to deal in
|
||
|
// the Software without restriction, including without limitation the rights to
|
||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||
|
// of the Software, and to permit persons to whom the Software is furnished to do
|
||
|
// so, subject to the following conditions:
|
||
|
// The above copyright notice and this permission notice shall be included in all
|
||
|
// copies or substantial portions of the Software.
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
// SOFTWARE.
|
||
|
// ------------------------------------------------------------------------------
|
||
|
// ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||
|
// This is free and unencumbered software released into the public domain.
|
||
|
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||
|
// software, either in source code form or as a compiled binary, for any purpose,
|
||
|
// commercial or non-commercial, and by any means.
|
||
|
// In jurisdictions that recognize copyright laws, the author or authors of this
|
||
|
// software dedicate any and all copyright interest in the software to the public
|
||
|
// domain. We make this dedication for the benefit of the public at large and to
|
||
|
// the detriment of our heirs and successors. We intend this dedication to be an
|
||
|
// overt act of relinquishment in perpetuity of all present and future rights to
|
||
|
// this software under copyright law.
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||
|
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
// ------------------------------------------------------------------------------
|