From 77f7e654e807cb032ee1f53265dfe4c025188a90 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Jul 2026 22:39:36 -0700 Subject: [PATCH] Make the 'Angle Between' node report the signed angle and fix its zero-vector output (#4362) --- node-graph/nodes/math/src/lib.rs | 35 ++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 63170e02ea..879979030d 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -1293,11 +1293,23 @@ fn cross_product( /// Calculates the angle swept between two vec2s. /// -/// The value is always positive and ranges from 0° (both vec2s point the same direction) to 180° (both vec2s point opposite directions). +/// The angle ranges from -180° to 180° (or -π to π radians) and its sign gives the sweep direction from the "Direction From" input to the "Direction To" input: positive for clockwise, negative for counterclockwise, as drawn in the viewport and matching the direction convention of the Transform node's rotation. #[node_macro::node(category("Math: Vec2"))] -fn angle_between(_: impl Ctx, vector_a: DVec2, vector_b: DVec2, radians: bool) -> f64 { - let dot_product = vector_a.normalize_or_zero().dot(vector_b.normalize_or_zero()); - let angle = dot_product.acos(); +fn angle_between( + _: impl Ctx, + /// The direction the angle is measured from. + direction_from: DVec2, + /// The direction the angle is measured to. + #[default(1., 0.)] + direction_to: DVec2, + /// Whether the resulting angle should be given in radians instead of degrees. + radians: bool, +) -> f64 { + if direction_from == DVec2::ZERO || direction_to == DVec2::ZERO { + return 0.; + } + + let angle = direction_from.angle_to(direction_to); if radians { angle } else { angle.to_degrees() } } @@ -1430,6 +1442,21 @@ mod test { assert!(lerp_between(5., -0., 1.).is_sign_negative()); } + #[test] + pub fn angle_between_signed() { + let right = DVec2::new(1., 0.); + let down = DVec2::new(0., 1.); + let angle = |a, b, radians| angle_between(&(), a, b, radians); + assert_eq!(angle(right, down, false), 90.); + assert_eq!(angle(down, right, false), -90.); + } + + #[test] + pub fn angle_between_zero_vector() { + let (zero, right) = (DVec2::ZERO, DVec2::new(1., 0.)); + assert_eq!(angle_between(&(), zero, right, false), 0.); + } + #[test] pub fn clamp_vec2_within_swapped_bounds() { let vec2 = |x, y| DVec2::new(x, y);