feat(noise): add preserveLength toggle

When enabled (default), perturbs each segment's direction vector and
rescales to original length — bends the path without stretching it or
causing fold-back artifacts. When disabled, the original direct point
displacement is used.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 23:52:24 +02:00
parent f652b712df
commit 581daa1be7
2 changed files with 76 additions and 58 deletions
@@ -52,6 +52,11 @@
"max": 5, "max": 5,
"value": 1, "value": 1,
"hidden": true "hidden": true
},
"preserveLength": {
"type": "boolean",
"label": "Preserve length",
"value": true
} }
} }
} }
+71 -58
View File
@@ -31,6 +31,7 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
let depth = evaluate_int(args[6]); let depth = evaluate_int(args[6]);
let octaves = evaluate_int(args[7]); let octaves = evaluate_int(args[7]);
let preserve_length = evaluate_int(args[8]) != 0;
let noise_x: HybridMulti<OpenSimplex> = let noise_x: HybridMulti<OpenSimplex> =
HybridMulti::new(seed as u32 + 1).set_octaves(octaves as usize); HybridMulti::new(seed as u32 + 1).set_octaves(octaves as usize);
@@ -66,70 +67,82 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
let length = path.get_length() as f64; let length = path.get_length() as f64;
// Record original segment lengths so we can re-project after displacement if preserve_length {
let seg_lens: Vec<f32> = (0..path.length - 1) // Snapshot original positions so we can derive each segment's original
.map(|k| { // direction even after we've modified earlier points.
let p0 = Vec3::new( let orig: Vec<f32> = path.points[..path.length * 4].to_vec();
path.points[k * 4],
path.points[k * 4 + 1], // Anchor the base (fix_bottom=1 → scale=0, no displacement at root)
path.points[k * 4 + 2], 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;
let sf = lerp(1.0, a as f32, fix_bottom);
let orig_dir = Vec3::new(
orig[i * 4] - orig[(i - 1) * 4],
orig[i * 4 + 1] - orig[(i - 1) * 4 + 1],
orig[i * 4 + 2] - orig[(i - 1) * 4 + 2],
); );
let p1 = Vec3::new( let orig_len = orig_dir.length();
path.points[(k + 1) * 4],
path.points[(k + 1) * 4 + 1], let perturb = Vec3::new(
path.points[(k + 1) * 4 + 2], noise_x.get([px, py]) as f32 * directional_strength[0] * strength * sf,
noise_y.get([px, py]) as f32 * directional_strength[1] * strength * sf,
noise_z.get([px, py]) as f32 * directional_strength[2] * strength * sf,
); );
(p1 - p0).length()
})
.collect();
// Displace the first point (fix_bottom=1 → scale=0 here, anchoring the base) // Perturb the original direction and rescale to original length.
let scale0 = lerp(1.0, 0.0, fix_bottom); // Biasing toward orig_dir prevents the segment from folding back.
path.points[0] += noise_x.get([j as f64, 0.0]) as f32 let mut new_dir = orig_dir + perturb;
* directional_strength[0] let nd_len = new_dir.length();
* strength if nd_len > 0.0001 && orig_len > 0.0001 {
* scale0; new_dir *= orig_len / nd_len;
path.points[1] += noise_y.get([j as f64, 0.0]) as f32 } else {
* directional_strength[1] new_dir = orig_dir;
* 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 cur = prev + new_dir;
let a = i as f64 / (path.length - 1) as f64; path.points[i * 4] = cur.x;
path.points[i * 4 + 1] = cur.y;
let px = j as f64 + a * length * scale; path.points[i * 4 + 2] = cur.z;
let py = a * scale as f64;
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; prev = cur;
} }
} else {
for i in 0..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;
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;
}
} }
path_data path_data
}) })