From 0677dd146c12480e00790124545d7a2502660863 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 9 Aug 2026 01:47:04 +0300 Subject: [PATCH] =?UTF-8?q?refactor(blend):=20evaluate=20the=20B=C3=A9zier?= =?UTF-8?q?=20with=20de=20Casteljau=20from=20the=20kernel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BLEND summed Bernstein terms using a binomial-coefficient table with entries for degrees three and five — the two it builds — and a fallback of one for everything else. Correct today, and quietly wrong the moment a third continuity mode is added. De Casteljau needs no coefficients at all, works at any degree, and is steadier: the Bernstein terms are large coefficients times small powers and they cancel, while repeated interpolation only ever averages neighbours. Curvature through three points moves with it, since both are the 3D arithmetic a blend needs and neither has a planar counterpart to delegate to. Co-Authored-By: Claude Opus 5 --- Cargo.lock | 2 +- src/modules/draw/modify/blend.rs | 59 +++++++++++--------------------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 565f6a2f8..d59ee5617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -935,7 +935,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git#e4c9fe626a60fd1500e8f2264e5a712e939c4027" +source = "git+https://github.com/HakanSeven12/cadkernel.git#69a8bf2b747c8b0a4abe6a571859c4c3a4f610d2" dependencies = [ "cavalier_contours", ] diff --git a/src/modules/draw/modify/blend.rs b/src/modules/draw/modify/blend.rs index 5636e32ed..faadd4a3c 100644 --- a/src/modules/draw/modify/blend.rs +++ b/src/modules/draw/modify/blend.rs @@ -6,6 +6,7 @@ //! geometric curvature (G2). use acadrust::entities::{EntityCommon, Spline}; +use acadrust::kernel::space::curve as space_curve; use acadrust::types::Vector3; use acadrust::{EntityType, Handle}; use glam::DVec3; @@ -628,49 +629,29 @@ fn curve_frame( }) } +/// The curvature vector at `point` of the circle through the three points. fn circumcircle_curvature(point: DVec3, next: DVec3, third: DVec3) -> DVec3 { - let u = next - point; - let v = third - point; - let cross = u.cross(v); - let denominator = 2.0 * cross.length_squared(); - if denominator <= EPS { - return DVec3::ZERO; - } - let center_offset = - (u.length_squared() * v.cross(cross) + v.length_squared() * cross.cross(u)) / denominator; - let radius_squared = center_offset.length_squared(); - if radius_squared <= EPS { - DVec3::ZERO - } else { - center_offset / radius_squared - } + let curvature = space_curve::curvature_through( + [point.x, point.y, point.z], + [next.x, next.y, next.z], + [third.x, third.y, third.z], + ); + DVec3::new(curvature[0], curvature[1], curvature[2]) } +/// A Bézier control polygon sampled into a polyline. +/// +/// De Casteljau, from the kernel. What was here summed Bernstein terms with +/// binomial coefficients out of a lookup table that had entries for degrees +/// three and five and returned one for everything else — correct only for +/// the two the blend happens to build today, and quietly wrong the moment a +/// third was added. fn sample_bezier(control_points: &[Vector3], segments: usize) -> Vec<[f64; 3]> { - let degree = control_points.len().saturating_sub(1); - (0..=segments) - .map(|step| { - let t = step as f64 / segments as f64; - let mut point = DVec3::ZERO; - for (index, control) in control_points.iter().enumerate() { - let coefficient = binomial(degree, index) as f64 - * t.powi(index as i32) - * (1.0 - t).powi((degree - index) as i32); - point += coefficient * to_dvec(*control); - } - [point.x, point.y, point.z] - }) - .collect() -} - -fn binomial(n: usize, k: usize) -> usize { - match (n, k) { - (3, 0 | 3) | (5, 0 | 5) => 1, - (3, 1 | 2) => 3, - (5, 1 | 4) => 5, - (5, 2 | 3) => 10, - _ => 1, - } + let control: Vec<[f64; 3]> = control_points + .iter() + .map(|p| [p.x, p.y, p.z]) + .collect(); + space_curve::bezier_points(&control, segments) } fn ocs_to_dvec(x: f64, y: f64, z: f64, normal: DVec3) -> DVec3 {