refactor(blend): evaluate the Bézier with de Casteljau from the kernel

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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5 committed 2026-08-09 01:47:04 +03:00
1 parent 77a9167e34
commit 0677dd146c
2 files changed
+21 -40

No files matched your search

Generated
+1 -1
View File
@@ -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",
]
+20 -39
View File
@@ -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 {