From a8d97345dc9c369d04967b666aec5c31e3be9be3 Mon Sep 17 00:00:00 2001 From: treeform Date: Mon, 25 May 2026 12:56:26 -0700 Subject: [PATCH] Add step and smoothstep functions --- README.md | 2 ++ src/vmath.nim | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.nim | 21 +++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/README.md b/README.md index 3839e79..42313f7 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ quat(1.0, 2.0, 3.0, 4.0) ~= quat(1.0, 2.0, 3.0, 4.0) * `sign` - Returns the sign of a number, -1 or 1. * `quantize` - Makes v be a multiple of n. Rounding to integer quantizes by 1.0. * `lerp` - Interpolates value between a and b. +* `step` - Returns 0.0 if x is less than edge, otherwise 1.0. +* `smoothstep` - Performs smooth Hermite interpolation between 0.0 and 1.0. ## Angle functions diff --git a/src/vmath.nim b/src/vmath.nim index 6a98d29..833e2e3 100644 --- a/src/vmath.nim +++ b/src/vmath.nim @@ -558,6 +558,15 @@ proc mix*[T: SomeFloat](a, b, v: T): T = ## * 0.5 -> between a and b v * (b - a) + a +proc step*[T: SomeFloat](edge, x: T): T = + ## Returns 0.0 if x is less than edge, otherwise 1.0. + if x < edge: T(0) else: T(1) + +proc smoothstep*[T: SomeFloat](edge0, edge1, x: T): T = + ## Performs smooth Hermite interpolation between 0.0 and 1.0. + let t = clamp((x - edge0) / (edge1 - edge0), T(0), T(1)) + t * t * (T(3) - T(2) * t) + proc fixAngle*[T: SomeFloat](angle: T): T = ## Normalize the angle to be from -PI to PI radians. result = angle @@ -953,6 +962,66 @@ proc mix*[T: SomeFloat](a, b, v: GVec4[T]): type(a) = result.z = a.z * (1.0 - v.z) + b.z * v.z result.w = a.w * (1.0 - v.w) + b.w * v.w +proc step*[T: SomeFloat](edge, x: GVec2[T]): type(x) = + result.x = step(edge.x, x.x) + result.y = step(edge.y, x.y) + +proc step*[T: SomeFloat](edge, x: GVec3[T]): type(x) = + result.x = step(edge.x, x.x) + result.y = step(edge.y, x.y) + result.z = step(edge.z, x.z) + +proc step*[T: SomeFloat](edge, x: GVec4[T]): type(x) = + result.x = step(edge.x, x.x) + result.y = step(edge.y, x.y) + result.z = step(edge.z, x.z) + result.w = step(edge.w, x.w) + +proc step*[T: SomeFloat](edge: T, x: GVec2[T]): type(x) = + result.x = step(edge, x.x) + result.y = step(edge, x.y) + +proc step*[T: SomeFloat](edge: T, x: GVec3[T]): type(x) = + result.x = step(edge, x.x) + result.y = step(edge, x.y) + result.z = step(edge, x.z) + +proc step*[T: SomeFloat](edge: T, x: GVec4[T]): type(x) = + result.x = step(edge, x.x) + result.y = step(edge, x.y) + result.z = step(edge, x.z) + result.w = step(edge, x.w) + +proc smoothstep*[T: SomeFloat](edge0, edge1, x: GVec2[T]): type(x) = + result.x = smoothstep(edge0.x, edge1.x, x.x) + result.y = smoothstep(edge0.y, edge1.y, x.y) + +proc smoothstep*[T: SomeFloat](edge0, edge1, x: GVec3[T]): type(x) = + result.x = smoothstep(edge0.x, edge1.x, x.x) + result.y = smoothstep(edge0.y, edge1.y, x.y) + result.z = smoothstep(edge0.z, edge1.z, x.z) + +proc smoothstep*[T: SomeFloat](edge0, edge1, x: GVec4[T]): type(x) = + result.x = smoothstep(edge0.x, edge1.x, x.x) + result.y = smoothstep(edge0.y, edge1.y, x.y) + result.z = smoothstep(edge0.z, edge1.z, x.z) + result.w = smoothstep(edge0.w, edge1.w, x.w) + +proc smoothstep*[T: SomeFloat](edge0, edge1: T, x: GVec2[T]): type(x) = + result.x = smoothstep(edge0, edge1, x.x) + result.y = smoothstep(edge0, edge1, x.y) + +proc smoothstep*[T: SomeFloat](edge0, edge1: T, x: GVec3[T]): type(x) = + result.x = smoothstep(edge0, edge1, x.x) + result.y = smoothstep(edge0, edge1, x.y) + result.z = smoothstep(edge0, edge1, x.z) + +proc smoothstep*[T: SomeFloat](edge0, edge1: T, x: GVec4[T]): type(x) = + result.x = smoothstep(edge0, edge1, x.x) + result.y = smoothstep(edge0, edge1, x.y) + result.z = smoothstep(edge0, edge1, x.z) + result.w = smoothstep(edge0, edge1, x.w) + proc cross*[T](a, b: GVec3[T]): GVec3[T] = gvec3( a.y * b.z - a.z * b.y, diff --git a/tests/tests.nim b/tests/tests.nim index 56e02df..f0e7a52 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -55,6 +55,17 @@ suite "scalar utilities": check mix(0.0, 10.0, 0.5) ~= 5.0 check mix(-1.0, 1.0, 0.25) ~= -0.5 + test "step": + check step(0.5, 0.25) ~= 0.0 + check step(0.5, 0.5) ~= 1.0 + check step(0.5'f32, 1.0'f32) ~= 1.0'f32 + + test "smoothstep": + check smoothstep(0.0, 1.0, -1.0) ~= 0.0 + check smoothstep(0.0, 1.0, 0.5) ~= 0.5 + check smoothstep(0.0, 1.0, 2.0) ~= 1.0 + check smoothstep(0.0'f32, 1.0'f32, 0.25'f32) ~= 0.15625'f32 + test "fixAngle": check fixAngle(0.1) ~= 0.1 check fixAngle(3.1) ~= 3.1 @@ -622,6 +633,16 @@ suite "vector operations": check mix(vec2(0, 0), vec2(10, 20), vec2(0.5, 1.0)) ~= vec2(5, 20) check mix(vec3(0, 0, 0), vec3(10, 20, 30), vec3(0.0, 0.5, 1.0)) ~= vec3(0, 10, 30) + test "step (vector)": + check step(vec2(0.5, 0.5), vec2(0.25, 0.5)) == vec2(0, 1) + check step(0.5'f32, vec3(0.25, 0.5, 0.75)) == vec3(0, 1, 1) + check step(dvec2(-1, 2), dvec2(-2, 3)) == dvec2(0, 1) + + test "smoothstep (vector)": + check smoothstep(0.0'f32, 1.0'f32, vec3(-1, 0.5, 2)) ~= vec3(0, 0.5, 1) + check smoothstep(vec3(0, 0, 0), vec3(1, 2, 4), vec3(0.5, 1, 2)) ~= vec3(0.5, 0.5, 0.5) + check smoothstep(dvec2(0, 0), dvec2(1, 1), dvec2(0.25, 0.75)) ~= dvec2(0.15625, 0.84375) + test "clamp (vector bounds)": check clamp(vec2(5, -5), vec2(0, 0), vec2(3, 3)) == vec2(3, 0) check clamp(vec3(5, -5, 1), vec3(0, 0, 0), vec3(3, 3, 3)) == vec3(3, 0, 1)