From 416d6adc9965b1568eee3f91325ece5379880815 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 27 Apr 2026 02:29:41 +0300 Subject: [PATCH] feat: MultiLeader spline path type via Catmull-Rom tessellation When path_type == Spline, the leader line is now tessellated as a Catmull-Rom spline through the bend points (8 segments per span) instead of connecting them with straight segments. Straight/Invisible path types are unchanged. Key vertices and tangent geoms still use the raw control points for snap and tangent snapping. Co-Authored-By: Claude Sonnet 4.6 --- src/entities/multileader.rs | 64 ++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/entities/multileader.rs b/src/entities/multileader.rs index fb7d00c54..6ae6a1824 100644 --- a/src/entities/multileader.rs +++ b/src/entities/multileader.rs @@ -10,6 +10,34 @@ use crate::scene::wire_model::TangentGeom; // ── TruckConvertible ──────────────────────────────────────────────────────── +/// Catmull-Rom spline tessellation through `ctrl` points, `segs_per_span` segments each. +fn catmull_rom_pts(ctrl: &[[f32; 3]], segs_per_span: u32) -> Vec<[f32; 3]> { + let n = ctrl.len(); + let mut out = Vec::new(); + for i in 0..n.saturating_sub(1) { + let p0 = if i == 0 { ctrl[0] } else { ctrl[i - 1] }; + let p1 = ctrl[i]; + let p2 = ctrl[i + 1]; + let p3 = if i + 2 < n { ctrl[i + 2] } else { ctrl[n - 1] }; + for j in 0..=segs_per_span { + let t = j as f32 / segs_per_span as f32; + let t2 = t * t; + let t3 = t2 * t; + let mut pt = [0.0_f32; 3]; + for k in 0..3 { + pt[k] = 0.5 * ( + (2.0 * p1[k]) + + (-p0[k] + p2[k]) * t + + (2.0 * p0[k] - 5.0 * p1[k] + 4.0 * p2[k] - p3[k]) * t2 + + (-p0[k] + 3.0 * p1[k] - 3.0 * p2[k] + p3[k]) * t3 + ); + } + out.push(pt); + } + } + out +} + fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option { let nan = [f32::NAN; 3]; let p3 = |v: &acadrust::types::Vector3| -> [f32; 3] { [v.x as f32, v.y as f32, v.z as f32] }; @@ -34,25 +62,31 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option = line.points.iter().map(|p| p3(p)).collect(); + let last_f = *ctrl.last().unwrap_or(&cp_f); let dist = ((last_f[0]-cp_f[0]).powi(2) + (last_f[1]-cp_f[1]).powi(2)).sqrt(); if dist > 1e-9 { - points.push(cp_f); - key_verts.push(cp_f); + ctrl.push(cp_f); + } + for &c in &ctrl { + key_verts.push(c); } - for i in 0..line.points.len().saturating_sub(1) { - tangents.push(TangentGeom::Line { - p1: p3(&line.points[i]), - p2: p3(&line.points[i + 1]), - }); + if ml.path_type == MultiLeaderPathType::Spline && ctrl.len() >= 2 { + // Catmull-Rom spline through the bend points. + let pts = catmull_rom_pts(&ctrl, 8); + for &pt in &pts { + points.push(pt); + } + } else { + for &c in &ctrl { + points.push(c); + } + } + + for i in 0..ctrl.len().saturating_sub(1) { + tangents.push(TangentGeom::Line { p1: ctrl[i], p2: ctrl[i + 1] }); } }