From 5dbcdaf0c2df3b1f642d4e2d853b5534605c00cd Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Jul 2026 18:41:28 -0700 Subject: [PATCH] Fix the 'Divide' node dividing partially-zero Vec2 denominators and the 'Logarithm' node misdetecting base e (#4359) --- node-graph/nodes/math/src/lib.rs | 64 ++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index f3d505fd01..468a0c3465 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -10,7 +10,7 @@ use math_parser::context::{EvalContext, NothingMap, ValueProvider}; use math_parser::value::{Number, Value}; use num_traits::Pow; use rand::{Rng, SeedableRng}; -use std::ops::{Add, Div, Mul, Rem, Sub}; +use std::ops::{Add, Mul, Rem, Sub}; use vector_types::Gradient; use vector_types::markers::{GradientType as GradientTypeAttr, SpreadMethod as SpreadMethodAttr}; @@ -121,11 +121,52 @@ fn multiply, B>( multiplier * multiplicand } +pub trait SafeDivide { + type Output; + fn safe_divide(self, denominator: Rhs) -> Self::Output; +} +impl SafeDivide for f64 { + type Output = f64; + fn safe_divide(self, denominator: f64) -> f64 { + if denominator == 0. { 0. } else { self / denominator } + } +} +impl SafeDivide for f32 { + type Output = f32; + fn safe_divide(self, denominator: f32) -> f32 { + if denominator == 0. { 0. } else { self / denominator } + } +} +impl SafeDivide for u32 { + type Output = u32; + fn safe_divide(self, denominator: u32) -> u32 { + self.checked_div(denominator).unwrap_or(0) + } +} +impl SafeDivide for DVec2 { + type Output = DVec2; + fn safe_divide(self, denominator: DVec2) -> DVec2 { + DVec2::new(self.x.safe_divide(denominator.x), self.y.safe_divide(denominator.y)) + } +} +impl SafeDivide for DVec2 { + type Output = DVec2; + fn safe_divide(self, denominator: f64) -> DVec2 { + DVec2::new(self.x.safe_divide(denominator), self.y.safe_divide(denominator)) + } +} +impl SafeDivide for f64 { + type Output = DVec2; + fn safe_divide(self, denominator: DVec2) -> DVec2 { + DVec2::new(self.safe_divide(denominator.x), self.safe_divide(denominator.y)) + } +} + /// The division operation (`รท`) calculates the quotient of two scalar numbers or vectors. /// -/// Produces 0 if the denominator is 0. +/// Produces 0 for any division by 0. With vec2 inputs, this applies separately to the X and Y components. #[node_macro::node(category("Math: Arithmetic"))] -fn divide + Default + PartialEq, B: Default + PartialEq>( +fn divide, B>( _: impl Ctx, /// The left-hand side of the division operation. #[implementations(f64, f32, u32, DVec2, DVec2, f64)] @@ -134,14 +175,8 @@ fn divide + Default + PartialEq, B: Default + PartialEq>( #[default(1.)] #[implementations(f64, f32, u32, DVec2, f64, DVec2)] denominator: B, -) -> >::Output -where - >::Output: Default, -{ - if denominator == B::default() { - return >::Output::default(); - } - numerator / denominator +) -> >::Output { + numerator.safe_divide(denominator) } /// The reciprocal operation (`1/x`) calculates the multiplicative inverse of a number. @@ -235,7 +270,7 @@ fn logarithm( value.log2() } else if base == T::from(10.).unwrap() { value.log10() - } else if base - T::from(std::f64::consts::E).unwrap() < T::epsilon() * T::from(1e6).unwrap() { + } else if (base - T::from(std::f64::consts::E).unwrap()).abs() < T::epsilon() * T::from(1e6).unwrap() { value.ln() } else { value.log(base) @@ -1051,6 +1086,11 @@ mod test { assert_eq!(super::divide(&(), DVec2::ONE, 2_f64), DVec2::ONE / 2.); } + #[test] + pub fn divide_vector_by_partially_zero_vector() { + assert_eq!(super::divide(&(), DVec2::new(1., 2.), DVec2::new(2., 0.)), DVec2::new(0.5, 0.)); + } + #[test] pub fn modulo_positive() { assert_eq!(super::modulo(&(), -5_f64, 2_f64, true), 1_f64);