Skip to content

Add smoothstep node#4189

Open
benstockil wants to merge 3 commits into
GraphiteEditor:masterfrom
benstockil:feat/smoothstep-node
Open

Add smoothstep node#4189
benstockil wants to merge 3 commits into
GraphiteEditor:masterfrom
benstockil:feat/smoothstep-node

Conversation

@benstockil

Copy link
Copy Markdown

Add smoothstep node, as per OpenGL reference.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread node-graph/nodes/math/src/lib.rs Outdated
Comment thread node-graph/nodes/math/src/lib.rs Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file

Confidence score: 3/5

  • There is a concrete regression risk in node-graph/nodes/math/src/lib.rs: when edge_min == edge_max, the denominator becomes 0.0, yielding NaN that 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.rs is documentation mismatch for edge_max (docs say outputs map to 0, while smoothstep behavior maps above the upper edge to 1).
  • Pay close attention to node-graph/nodes/math/src/lib.rs - add a zero-width-edge guard and align edge_max docs with actual smoothstep output.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread node-graph/nodes/math/src/lib.rs Outdated
Comment thread node-graph/nodes/math/src/lib.rs Outdated
Comment thread node-graph/nodes/math/src/lib.rs Outdated
Comment on lines +574 to +582
#[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.)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just f64

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Keavon

Keavon commented Jun 9, 2026

Copy link
Copy Markdown
Member

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.

@Keavon Keavon marked this pull request as draft June 9, 2026 04:41
@benstockil

Copy link
Copy Markdown
Author

Apologies for the delay! I've replaced the generic floats with f64s.

@benstockil benstockil marked this pull request as ready for review June 19, 2026 16:43

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Confidence score: 4/5

  • In node-graph/nodes/math/src/lib.rs, smoothstep being hardcoded to f64 while nearby math nodes are generic can cause type-mismatch friction in f32-based graphs and reduce node interoperability if merged as-is; align it with the Float pattern 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

Comment on lines +571 to 593
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants