Skip to main content

vello_common/
math.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Mathematical helper functions.
5
6use core::ops::Sub;
7#[cfg(not(feature = "std"))]
8use peniko::kurbo::common::FloatFuncs as _;
9
10/// Round `value` up to the next multiple of `step`.
11#[inline]
12pub(crate) fn snap_up(value: f64, step: u16) -> f64 {
13    let step = f64::from(step);
14    (value / step).ceil() * step
15}
16
17// See https://raphlinus.github.io/audio/2018/09/05/sigmoid.html for a little
18// explanation of this approximation to the erf function.
19/// Approximate the erf function.
20pub fn compute_erf7(x: f32) -> f32 {
21    // Clamp `x`, because for large `x` the terms here become `inf`, causing the result to be 0 or
22    // `NaN`. This clamping doesn't lose any information, because `erf(±10) ≈ 1` well within `f64`
23    // machine precision, let alone `f32`.
24    let x = x.clamp(-10., 10.);
25    let x = x * core::f32::consts::FRAC_2_SQRT_PI;
26    let xx = x * x;
27    let x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx);
28    x / (1.0 + x * x).sqrt()
29}
30
31// From <https://github.com/linebender/tiny-skia/blob/68b198a7210a6bbf752b43d6bc4db62445730313/path/src/scalar.rs#L12>
32// Note: If this value changes, also update NEARLY_ZERO_TOLERANCE in render.wesl
33// @see {@link https://github.com/linebender/vello/blob/58b80d660e2fc5aef3bd32b24af3f95e973aab95/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl#L63}
34const SCALAR_NEARLY_ZERO: f32 = 1.0 / (1 << 12) as f32;
35
36/// A number of useful methods for floating-point numbers.
37pub trait FloatExt: Sized + Sub<Self, Output = Self> {
38    /// Whether the number is approximately 0.
39    fn is_nearly_zero(&self) -> bool;
40
41    /// Whether the number is approximately 0, with a given tolerance.
42    fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool;
43}
44
45impl FloatExt for f32 {
46    fn is_nearly_zero(&self) -> bool {
47        self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
48    }
49
50    fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
51        debug_assert!(tolerance >= 0.0, "tolerance must be positive");
52
53        self.abs() <= tolerance
54    }
55}
56
57impl FloatExt for f64 {
58    fn is_nearly_zero(&self) -> bool {
59        self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
60    }
61
62    fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
63        debug_assert!(tolerance >= 0.0, "tolerance must be positive");
64
65        self.abs() <= tolerance as Self
66    }
67}