-
released this
2026-10-24 13:12:09 +02:00 | 0 commits to main since this releaseorthographicOffCenter*take (left, right, bottom, top, near, far)
(breaking, silently): the order of DirectXMath and GLM. They took top before
bottom, inherited from zmath. All six parameters aref32, so a call in the
old order still compiles -- and flips the projection vertically. Swap the
third and fourth arguments of every call.- NaN propagates through
min,max,clamp,saturate(breaking): they
returned the other operand, soclamp(NaN, 0, 1)was 0 and a NaN colour came
out ofrgbToHsl/rgbToSrgbas a plausible one. Now NaN in gives NaN, and
the colour functions follow. The old behaviour is there by name:minNum,
maxNum,clampNum,saturateNum(IEEE 754 minimumNumber;@min/@max,
one instruction on arm64). The*Fastforms are documented as NaN result
unspecified, for finite input only. asinandacosare NaN outside [-1, 1] (breaking): they clamped, so
asin(1.1)andasin(100000)were pi/2. That is DirectXMath's SIMD
behaviour; Sony's Vector Math, NumPy and IEEE give NaN, as Lin now does. For
input that can overshoot by rounding, clamp first:acos(clamp(d, -1, 1)).mul(v, m)follows the FMA mode: vector times matrix was a plain
multiply-and-add in every mode. It now uses the same twomulAddchains as
mul(m0, m1), somul(v, m)is bit for bit the matching row of a matrix
product (in strict mode too, where the summation order changed). A test pins
what each mode does: one rounding or two.lerpandlerpVfollow the FMA mode: one subtract and onemulAdd
(they were a plain multiply and add in every mode). Their comment now says
what they guarantee: exact at t = 0 and for equal ends, exact at t = 1 only
up to rounding.- Stricter tests: trig tolerances from the measured errors (
acoswas
checked to 0.1, now 1e-6), tests forlerp,adjustContrast,norIntand
every matrix load/store layout, and the FMA modes' rounding pinned. - CI on the urverk forge:
.forgejo/workflows/test.ymlrunsscripts/gate
on every push, on thezig-arm64runner with Zig 0.17.0. zmath's GitHub
workflow (ubuntu/windows/macos runners, which the forge does not have) is
gone; it never ran.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v0.1.0
Stablereleased this
2026-10-23 12:05:25 +02:00 | 15 commits to main since this release- No more x86 inline assembly in
round/floor/ceil/trunc: on an
AVX-512 target in Debug (Zig's own x86_64 backend), Lin did not compile
(genSetReg called with a value larger than dst_reg, from assembly pinning
16-wide vectors to zmm0) -- including a native Debug build on any AVX-512 CPU.
floor/ceil/truncare Zig's builtins on every target,roundthe
portable ties-to-even code; the same rounding code now runs on every CPU. - FMA modes (breaking): every function exists as
lin.strict.*,
lin.fused.*andlin.relaxed.*; the top-level names are the mode chosen
with-Dfma(defaultfused). Fused uses a fused multiply-add at Lin's
multiply-add sites on every target -- the same results on every CPU with FMA,
and on arm64 ~25% faster matrix multiplies than before, when zmath never fused
off x86. Theenable_cross_platform_determinismoption is gone; strict is
what it gave. normalizeOrZero2/3/4: likenormalizeN, but a zero-length vector
gives the zero vector.normalizeNkeeps returning NaN for it, so the
silent case is always an explicit choice; NaN or infinite input is NaN in
both.slerpreturns a unit quaternion for nearly equal inputs: below ~0.009
rad apart it interpolates linearly and did not normalize, so the result was
up to ~1e-6 short of unit length.round,floor,ceil,truncgive the same results on every CPU:
off x86 (arm64), a zero result lost its sign (round(-0.4),trunc(-0.3),
ceil(-0.5)gave +0 where x86 gives -0, so1/xdiffered).floor,ceil
andtruncnow use Zig's IEEE builtins there (native instructions on
arm64, ~1.4x faster);roundkeeps ties-to-even and the input's sign.zig build testalso runs the tests in Debug: the default mode is
ReleaseFast, which has no safety checks, so a test hittingunreachableor
a bad cast could pass there.quatToRollPitchYawis accurate up to the poles: it snapped to the
pole once |pitch| > 1.507 rad, which put the rebuilt rotation off by up to
0.089 rad (5 degrees). It now extracts from the rotation matrix in f64, with
atan2for pitch and a pole switch at cos(pitch) < 1e-6; quaternion ->
angles -> quaternion is within 7.2e-7 rad everywhere (Bullet's
getEulerZYX: ~4.5e-3). Non-unit quaternions work; zero or NaN give NaN.inverseno longer hides a singular input (breaking): a matrix with
|det| < 2.2e-16 came back as all zeros, which caught small invertible
matrices too (a uniform scale of 1e-6), and a zero quaternion's inverse was
zero. Now a singular matrix gives Inf/NaN entries (useinverseDetto check
the determinant first), small matrices invert correctly (also at det 1e-39),
and the zero quaternion's inverse is NaN.util.getScaleVecandutil.getRotationQuatread rows (fix): they
measured columns, which is wrong for Lin's row vectors with any rotation
and non-uniform scale. Form = S * R * T(matFromSrt) they now return S
and R, with a mirror (det < 0) as a negative x scale; recomposing givesm
back. An axis with zero scale gives a NaN rotation.util's tests run: they sat in a nested struct nothing referenced,
so they were never compiled (and no longer compiled at all). Six tests
that had never run now do.hsvToRgbandhslToRgbwrap the hue: a hue below 0 reached
unreachableinhsvToRgb(a panic, or undefined behaviour in ReleaseFast),
andhslToRgbwrapped only one turn. Any finite hue now wraps into [0, 1).
A NaN or infinite hue gives NaN RGB (alpha kept), even at saturation 0;
hslToRgbused to turn it into a plausible colour.quatToAxisAnglereturns a real axis (breaking):axiswas the
quaternion itself (DirectXMath's convention) and is now the unit rotation
axis with w = 0. The angle is2*atan2(|xyz|, w), in [0, 2*pi]: accurate for
small angles (1e-3 rad came back as 9.77e-4) and the same for non-unit
quaternions. No rotation gives axis (1, 0, 0); a zero or NaN quaternion gives
NaN axis and angle.modAngle(f32)wraps correctly: it divided by pi where it meant 2*pi, so
modAngle(0.5)was -5.78 (errors up to 25 rad); it also trapped on NaN, Inf
or |x| > ~6.7e9. It now runs the vector version's computation: the result is
in [-pi, pi], equal bit for bit tomodAngle(F32x4), and NaN for NaN/Inf.matFromSrt(scale, rot, trans)builds a scale-rotate-translate matrix
directly: bit-identical tomul(mul(scalingV(s), quatToMat(q)), translationV(t))
and about 2.5x faster (2.9 ns against 7.2 on Apple Silicon).- Forked from zmath and renamed: package
lin, modulelin
(b.dependency("lin", .{}).module("lin"), wasmodule("root")), build
options modulelin_options. No behaviour change.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- No more x86 inline assembly in