diff --git a/vlib/math/interpolation.v b/vlib/math/interpolation.v index d2ce4f56cc..6ed74be10a 100644 --- a/vlib/math/interpolation.v +++ b/vlib/math/interpolation.v @@ -24,3 +24,38 @@ pub fn clip[T](x T, min_value T, max_value T) T { x } } + +// remap the input `x`, from the range [`a`,`b`] to [`c`,`d`] . +// Example: math.remap(20, 1, 100, 50, 5000) == 1000 +// Note: `a` should be != `b`. +@[inline] +pub fn remap[T](x T, a T, b T, c T, d T) T { + return c + (d - c) * (x - a) / (b - a) +} + +// smoothstep smoothly maps a value between `edge0` and `edge1`. It returns: +// 0 if `x` is less than or equal to the left `edge0`, +// 1 if `x` is greater than or equal to the right `edge`, +// and smoothly interpolates, using a Hermite polynomial, between 0 and 1 otherwise. +// The gradient of the smoothstep function is zero at both edges. This is convenient +// for creating a sequence of transitions using smoothstep to interpolate each segment +// as an alternative to using more sophisticated or expensive interpolation techniques. +// `smoothstep` is a 1st order smoothing function, using a 3rd order polynomial. +// See also `smootherstep`, which is slower, but nicer looking. +// See also https://en.wikipedia.org/wiki/Smoothstep +@[inline] +pub fn smoothstep[T](edge0 T, edge1 T, x T) T { + v := clip((x - edge0) / (edge1 - edge0), 0, 1) + return v * v * (3 - 2 * v) +} + +// smootherstep smoothly maps a value between `edge0` and `edge1`. +// smootherstep is a 2nd order smoothing function, using a 5th order polynomial. +// The 1st and 2nd order derivatives of the smootherstep function are 0 at both edges. +// See also `smoothstep`, which is faster, but has just its gradient being 0 at both edges. +// See also https://en.wikipedia.org/wiki/Smoothstep +@[inline] +pub fn smootherstep[T](edge0 T, edge1 T, x T) T { + v := clip((x - edge0) / (edge1 - edge0), 0, 1) + return v * v * v * (x * (6 * x - 15) + 10) +} diff --git a/vlib/math/interpolation_test.v b/vlib/math/interpolation_test.v index 1b73fe15a5..7135f7eee1 100644 --- a/vlib/math/interpolation_test.v +++ b/vlib/math/interpolation_test.v @@ -65,3 +65,40 @@ fn test_cubic_bezier_fa() { assert math.cubic_bezier_fa(0.5, bx_fa, by_fa) == math.BezierPoint{0.35375, 0.5375} assert math.cubic_bezier_fa(1.0, bx_fa, by_fa) == math.BezierPoint{bx_fa[3], by_fa[3]} } + +fn test_remap() { + assert math.remap(20, 1, 100, 50, 5000) == 1000 + assert math.remap(20.0, 1, 100, 50, 5000) == 1000.0 + + assert math.remap(55, 1, 100, 52, 5000) == 2750 + assert math.remap(55.0, 1, 100, 52, 5000) == 2750.909090909091 + + assert math.remap(25, 1, 100, 50, 5000) == 1250 + assert math.remap(25, 1, 100, -50, -5000) == -1250 + assert math.remap(25, 1, 100, 5000, 50) == 3800 + assert math.remap(25, 1, 100, -5000, -50) == -3800 + assert math.remap(25, 100, 1, 50, 5000) == 3800 + assert math.remap(25, 100, 1, -50, -5000) == -3800 + assert math.remap(25, 100, 1, 5000, 50) == 1250 + assert math.remap(25, 100, 1, -5000, -50) == -1250 +} + +fn test_smoothstep() { + assert math.smoothstep(0.0, 1, 0) == 0 + assert math.close(math.smoothstep(0.0, 1, 0.05), 0.00725) + assert math.close(math.smoothstep(0.0, 1, 0.1), 0.028) + assert math.smoothstep(0.0, 1, 0.5) == 0.5 + assert math.close(math.smoothstep(0.0, 1, 0.9), 0.972) + assert math.close(math.smoothstep(0.0, 1, 0.95), 0.99275) + assert math.smoothstep(0.0, 1, 1) == 1 +} + +fn test_smootherstep() { + assert math.smootherstep(0.0, 1, 0) == 0 + assert math.close(math.smootherstep(0.0, 1, 0.05), 0.001158125) + assert math.close(math.smootherstep(0.0, 1, 0.1), 0.00856) + assert math.smootherstep(0.0, 1, 0.5) == 0.5 + assert math.close(math.smootherstep(0.0, 1, 0.9), 0.99144) + assert math.close(math.smootherstep(0.0, 1, 0.95), 0.998841875) + assert math.smootherstep(0.0, 1, 1) == 1 +} diff --git a/vlib/math/math.v b/vlib/math/math.v index 965be67e07..23dd3391fe 100644 --- a/vlib/math/math.v +++ b/vlib/math/math.v @@ -42,6 +42,12 @@ pub fn degrees(radians f64) f64 { return radians * (180.0 / pi) } +// radians converts an angle in degrees to a corresponding angle in radians. +@[inline] +pub fn radians(degrees f64) f64 { + return degrees * (pi / 180.0) +} + // angle_diff calculates the difference between angles in radians @[inline] pub fn angle_diff(radian_a f64, radian_b f64) f64 { @@ -154,12 +160,6 @@ pub fn signi(n f64) int { return int(copysign(1.0, n)) } -// radians converts an angle in degrees to a corresponding angle in radians. -@[inline] -pub fn radians(degrees f64) f64 { - return degrees * (pi / 180.0) -} - // signbit returns a value with the boolean representation of the sign for x @[inline] pub fn signbit(x f64) bool {