From 68ae62527fba1af4dc6994cdb9cc12684da2683a Mon Sep 17 00:00:00 2001 From: Max Richter Date: Thu, 7 May 2026 23:43:44 +0200 Subject: [PATCH] feat(noise): preserve segment lengths during displacement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The noise node previously displaced each path point's XYZ independently, which stretched/compressed segments and produced kinked edges. After displacement, re-project each point onto the sphere of radius seg_lens[i-1] centered at the previous point — same pattern used by the gravity node. Total path length is now preserved; noise bends the path rather than stretching it. Co-Authored-By: Claude Opus 4.7 --- Cargo.lock | 1 + nodes/max/plantarium/noise/Cargo.toml | 1 + nodes/max/plantarium/noise/src/lib.rs | 73 ++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf43310..65725b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,7 @@ dependencies = [ name = "noise" version = "0.1.0" dependencies = [ + "glam", "nodarium_macros", "nodarium_utils", "noise 0.9.0", diff --git a/nodes/max/plantarium/noise/Cargo.toml b/nodes/max/plantarium/noise/Cargo.toml index 4d18f71..81f3a59 100644 --- a/nodes/max/plantarium/noise/Cargo.toml +++ b/nodes/max/plantarium/noise/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] +glam = "0.30.10" nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" } noise = "0.9.0" diff --git a/nodes/max/plantarium/noise/src/lib.rs b/nodes/max/plantarium/noise/src/lib.rs index b54f684..9f7dee8 100644 --- a/nodes/max/plantarium/noise/src/lib.rs +++ b/nodes/max/plantarium/noise/src/lib.rs @@ -1,3 +1,4 @@ +use glam::Vec3; use nodarium_macros::nodarium_definition_file; use nodarium_macros::nodarium_execute; use nodarium_utils::{ @@ -65,24 +66,70 @@ pub fn execute(input: &[i32]) -> Vec { let length = path.get_length() as f64; - for i in 0..path.length { + // Record original segment lengths so we can re-project after displacement + let seg_lens: Vec = (0..path.length - 1) + .map(|k| { + let p0 = Vec3::new( + path.points[k * 4], + path.points[k * 4 + 1], + path.points[k * 4 + 2], + ); + let p1 = Vec3::new( + path.points[(k + 1) * 4], + path.points[(k + 1) * 4 + 1], + path.points[(k + 1) * 4 + 2], + ); + (p1 - p0).length() + }) + .collect(); + + // Displace the first point (fix_bottom=1 → scale=0 here, anchoring the base) + let scale0 = lerp(1.0, 0.0, fix_bottom); + path.points[0] += noise_x.get([j as f64, 0.0]) as f32 + * directional_strength[0] + * strength + * scale0; + path.points[1] += noise_y.get([j as f64, 0.0]) as f32 + * directional_strength[1] + * strength + * scale0; + path.points[2] += noise_z.get([j as f64, 0.0]) as f32 + * directional_strength[2] + * strength + * scale0; + let mut prev = Vec3::new(path.points[0], path.points[1], path.points[2]); + + for i in 1..path.length { let a = i as f64 / (path.length - 1) as f64; let px = j as f64 + a * length * scale; let py = a * scale as f64; - path.points[i * 4] += noise_x.get([px, py]) as f32 - * directional_strength[0] - * strength - * lerp(1.0, a as f32, fix_bottom); - path.points[i * 4 + 1] += noise_y.get([px, py]) as f32 - * directional_strength[1] - * strength - * lerp(1.0, a as f32, fix_bottom); - path.points[i * 4 + 2] += noise_z.get([px, py]) as f32 - * directional_strength[2] - * strength - * lerp(1.0, a as f32, fix_bottom); + let sf = lerp(1.0, a as f32, fix_bottom); + path.points[i * 4] += + noise_x.get([px, py]) as f32 * directional_strength[0] * strength * sf; + path.points[i * 4 + 1] += + noise_y.get([px, py]) as f32 * directional_strength[1] * strength * sf; + path.points[i * 4 + 2] += + noise_z.get([px, py]) as f32 * directional_strength[2] * strength * sf; + + // Re-project onto sphere of radius seg_lens[i-1] centered at prev + let cur = Vec3::new( + path.points[i * 4], + path.points[i * 4 + 1], + path.points[i * 4 + 2], + ); + let dir = cur - prev; + let dir_len = dir.length(); + if dir_len > 0.0001 { + let corrected = prev + dir * (seg_lens[i - 1] / dir_len); + path.points[i * 4] = corrected.x; + path.points[i * 4 + 1] = corrected.y; + path.points[i * 4 + 2] = corrected.z; + prev = corrected; + } else { + prev = cur; + } } path_data })