• v0.2.0 fb2b3c0f74

    v0.2.0
    All checks were successful
    test / test (push) Successful in 4m47s
    Stable

    sicher released this 2026-10-24 13:12:09 +02:00 | 0 commits to main since this release

    • orthographicOffCenter* 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 are f32, 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, so clamp(NaN, 0, 1) was 0 and a NaN colour came
      out of rgbToHsl/rgbToSrgb as 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 *Fast forms are documented as NaN result
      unspecified, for finite input only.
    • asin and acos are NaN outside [-1, 1] (breaking): they clamped, so
      asin(1.1) and asin(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 two mulAdd chains as
      mul(m0, m1), so mul(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.
    • lerp and lerpV follow the FMA mode: one subtract and one mulAdd
      (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 (acos was
      checked to 0.1, now 1e-6), tests for lerp, adjustContrast, norInt and
      every matrix load/store layout, and the FMA modes' rounding pinned.
    • CI on the urverk forge: .forgejo/workflows/test.yml runs scripts/gate
      on every push, on the zig-arm64 runner 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
  • v0.1.0 87c63bf269

    v0.1.0
    Some checks failed
    ci / lint-and-build-and-test (macos-latest) (push) Has been cancelled
    ci / lint-and-build-and-test (ubuntu-latest) (push) Has been cancelled
    ci / lint-and-build-and-test (windows-latest) (push) Has been cancelled
    Stable

    sicher released 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/trunc are Zig's builtins on every target, round the
      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.* and lin.relaxed.*; the top-level names are the mode chosen
      with -Dfma (default fused). 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. The enable_cross_platform_determinism option is gone; strict is
      what it gave.
    • normalizeOrZero2/3/4: like normalizeN, but a zero-length vector
      gives the zero vector. normalizeN keeps returning NaN for it, so the
      silent case is always an explicit choice; NaN or infinite input is NaN in
      both.
    • slerp returns 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, trunc give 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, so 1/x differed). floor, ceil
      and trunc now use Zig's IEEE builtins there (native instructions on
      arm64, ~1.4x faster); round keeps ties-to-even and the input's sign.
    • zig build test also runs the tests in Debug: the default mode is
      ReleaseFast, which has no safety checks, so a test hitting unreachable or
      a bad cast could pass there.
    • quatToRollPitchYaw is 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
      atan2 for 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.
    • inverse no 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 (use inverseDet to check
      the determinant first), small matrices invert correctly (also at det 1e-39),
      and the zero quaternion's inverse is NaN.
    • util.getScaleVec and util.getRotationQuat read rows (fix): they
      measured columns, which is wrong for Lin's row vectors with any rotation
      and non-uniform scale. For m = S * R * T (matFromSrt) they now return S
      and R, with a mirror (det < 0) as a negative x scale; recomposing gives m
      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.
    • hsvToRgb and hslToRgb wrap the hue: a hue below 0 reached
      unreachable in hsvToRgb (a panic, or undefined behaviour in ReleaseFast),
      and hslToRgb wrapped 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;
      hslToRgb used to turn it into a plausible colour.
    • quatToAxisAngle returns a real axis (breaking): axis was the
      quaternion itself (DirectXMath's convention) and is now the unit rotation
      axis with w = 0. The angle is 2*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 to modAngle(F32x4), and NaN for NaN/Inf.
    • matFromSrt(scale, rot, trans) builds a scale-rotate-translate matrix
      directly: bit-identical to mul(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, module lin
      (b.dependency("lin", .{}).module("lin"), was module("root")), build
      options module lin_options. No behaviour change.
    Downloads