Add smoothstep node#4189
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new smoothstep node in the math library to perform smooth Hermite interpolation. The review feedback points out a documentation error where edge_max is incorrectly described as mapping values above it to 0 instead of 1. Additionally, the feedback identifies a potential division-by-zero/NaN propagation issue when edge_min equals edge_max, and notes that the indentation of the new code should be updated to use tabs instead of spaces.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
2 issues found across 1 file
Confidence score: 3/5
- There is a concrete regression risk in
node-graph/nodes/math/src/lib.rs: whenedge_min == edge_max, the denominator becomes0.0, yieldingNaNthat can propagate through clamp and downstream math. - Given the severity (7/10) and high confidence, this is user-impacting behavior rather than a cosmetic issue, so merge risk is moderate until guarded.
- A secondary issue in
node-graph/nodes/math/src/lib.rsis documentation mismatch foredge_max(docs say outputs map to0, while smoothstep behavior maps above the upper edge to1). - Pay close attention to
node-graph/nodes/math/src/lib.rs- add a zero-width-edge guard and alignedge_maxdocs with actual smoothstep output.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| #[implementations(f64, f32)] | ||
| value: T, | ||
| /// The lower bound of the input range. Input values below this edge are mapped to 0. | ||
| #[implementations(f64, f32)] | ||
| #[default(0.)] | ||
| edge_min: T, | ||
| /// The upper bound of the input range. Input values above this edge are mapped to 1. | ||
| #[implementations(f64, f32)] | ||
| #[default(1.)] |
There was a problem hiding this comment.
I think it might be best to just make this node work on f64 and don't have it be generic over f32 and f64. This was previously required, but nowadays, we have automatic conversions to f64 values, so this should no longer be necessary. @Keavon thoughs?
|
I will mark this as a draft while awaiting the requested change. Please be sure to un-mark it as draft when you've completed it. |
|
Apologies for the delay! I've replaced the generic floats with f64s. |
There was a problem hiding this comment.
1 issue found across 1 file
Confidence score: 4/5
- In
node-graph/nodes/math/src/lib.rs,smoothstepbeing hardcoded tof64while nearby math nodes are generic can cause type-mismatch friction inf32-based graphs and reduce node interoperability if merged as-is; align it with theFloatpattern and add#[implementations(f64, f32)]before merging to de-risk cross-type graph behavior.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="node-graph/nodes/math/src/lib.rs">
<violation number="1" location="node-graph/nodes/math/src/lib.rs:571">
P2: `smoothstep` is hardcoded to `f64` instead of being generic over `Float` with `#[implementations(f64, f32)]`, creating inconsistency with adjacent math nodes and limiting graph type interoperability.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| fn smoothstep( | ||
| _: impl Ctx, | ||
| /// The value to be mapped, which is restricted to the range between the minimum and maximum values. | ||
| value: f64, | ||
| /// The lower bound of the input range. Input values below this edge are mapped to 0. | ||
| #[default(0.)] | ||
| edge_min: f64, | ||
| /// The upper bound of the input range. Input values above this edge are mapped to 1. | ||
| #[default(1.)] | ||
| edge_max: f64, | ||
| ) -> f64 { | ||
| let divisor = edge_max - edge_min; | ||
|
|
||
| // handle divison by zero | ||
| if divisor.abs() == 0.0 { | ||
| return if value < edge_min { 0.0 } else { 1.0 }; | ||
| } | ||
|
|
||
| let t = ((value - edge_min) / divisor).clamp(0.0, 1.0); | ||
| t * t * (3.0 - 2.0 * t) | ||
| } | ||
|
|
||
| /// The greatest common divisor (GCD) calculates the largest positive integer that divides both of the two input numbers without leaving a remainder. |
There was a problem hiding this comment.
P2: smoothstep is hardcoded to f64 instead of being generic over Float with #[implementations(f64, f32)], creating inconsistency with adjacent math nodes and limiting graph type interoperability.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/nodes/math/src/lib.rs, line 571:
<comment>`smoothstep` is hardcoded to `f64` instead of being generic over `Float` with `#[implementations(f64, f32)]`, creating inconsistency with adjacent math nodes and limiting graph type interoperability.</comment>
<file context>
@@ -566,6 +566,30 @@ fn clamp<T: std::cmp::PartialOrd>(
+/// Performs smooth Hermite interpolation between two values.
+#[node_macro::node(category("Math: Numeric"))]
+fn smoothstep(
+ _: impl Ctx,
+ /// The value to be mapped, which is restricted to the range between the minimum and maximum values.
</file context>
| fn smoothstep( | |
| _: impl Ctx, | |
| /// The value to be mapped, which is restricted to the range between the minimum and maximum values. | |
| value: f64, | |
| /// The lower bound of the input range. Input values below this edge are mapped to 0. | |
| #[default(0.)] | |
| edge_min: f64, | |
| /// The upper bound of the input range. Input values above this edge are mapped to 1. | |
| #[default(1.)] | |
| edge_max: f64, | |
| ) -> f64 { | |
| let divisor = edge_max - edge_min; | |
| // handle divison by zero | |
| if divisor.abs() == 0.0 { | |
| return if value < edge_min { 0.0 } else { 1.0 }; | |
| } | |
| let t = ((value - edge_min) / divisor).clamp(0.0, 1.0); | |
| t * t * (3.0 - 2.0 * t) | |
| } | |
| /// The greatest common divisor (GCD) calculates the largest positive integer that divides both of the two input numbers without leaving a remainder. | |
| fn smoothstep<T: num_traits::float::Float>( | |
| _: impl Ctx, | |
| /// The value to be mapped, which is restricted to the range between the minimum and maximum values. | |
| #[implementations(f64, f32)] | |
| value: T, | |
| /// The lower bound of the input range. Input values below this edge are mapped to 0. | |
| #[implementations(f64, f32)] | |
| #[default(0.)] | |
| edge_min: T, | |
| /// The upper bound of the input range. Input values above this edge are mapped to 1. | |
| #[implementations(f64, f32)] | |
| #[default(1.)] | |
| edge_max: T, | |
| ) -> T { | |
| let divisor = edge_max - edge_min; | |
| // handle division by zero | |
| if divisor.abs() < T::epsilon() { | |
| return if value < edge_min { T::zero() } else { T::one() }; | |
| } | |
| let t = ((value - edge_min) / divisor).max(T::zero()).min(T::one()); | |
| t * t * (T::from(3.0).unwrap() - T::from(2.0).unwrap() * t) | |
| } |
Add smoothstep node, as per OpenGL reference.