Class-A surfacing 3/3: blend curves, curved-edge fillets, the G2 corner and continuity analysis (#1642)
Thanks @midasdf.
This commit is contained in:
1 parent
3126962cd4
commit
bbd4f29ebb
43 files changed
+4753
-132
No files matched your search
@@ -153,7 +153,7 @@ pub fn spline_point(p: &[Vec2], control: bool, degree: u8, t: f64) -> Vec2 {
|
||||
return p.first().copied().unwrap_or_default();
|
||||
}
|
||||
if control {
|
||||
return bspline_at(p, degree.clamp(1, 7) as usize, t);
|
||||
return bspline_at(p, degree.clamp(1, solidcraft_geom::MAX_BEZIER_DEGREE as u8) as usize, t);
|
||||
}
|
||||
let spans = fit_spans(p);
|
||||
let n = spans.len().max(1);
|
||||
@@ -292,7 +292,7 @@ pub fn spline_polyline(p: &[Vec2], control: bool, degree: u8) -> Vec<Vec2> {
|
||||
return p.to_vec();
|
||||
}
|
||||
if control {
|
||||
return bspline_polyline(p, degree.clamp(1, 7) as usize);
|
||||
return bspline_polyline(p, degree.clamp(1, solidcraft_geom::MAX_BEZIER_DEGREE as u8) as usize);
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for (p0, p1, t0, t1) in fit_spans(p) {
|
||||
|
||||
@@ -137,7 +137,9 @@ impl H {
|
||||
let (points, weights) = match *seg {
|
||||
Seg2::Line { a, b } => (vec![a, b], vec![1.0; 2]),
|
||||
Seg2::Cubic { p0, p1, p2, p3 } => (vec![p0, p1, p2, p3], vec![1.0; 4]),
|
||||
Seg2::Bezier { n, p } if (1..=7).contains(&n) => (p[..=usize::from(n)].to_vec(), vec![1.0; usize::from(n) + 1]),
|
||||
Seg2::Bezier { n, p } if (1..=solidcraft_geom::MAX_BEZIER_DEGREE as u8).contains(&n) => {
|
||||
(p[..=usize::from(n)].to_vec(), vec![1.0; usize::from(n) + 1])
|
||||
}
|
||||
Seg2::Conic { a, apex, b, w } if w.is_finite() && w > 0.0 => (vec![a, apex, b], vec![1.0, w, 1.0]),
|
||||
_ => return Err(bad("unsupported exact free-form offset piece")),
|
||||
};
|
||||
|
||||
@@ -256,7 +256,7 @@ impl LinkGeom {
|
||||
(!*periodic || (!*control && *degree == 3 && crate::periodic_beziers(pts).is_ok()))
|
||||
&& (2..=crate::curves::MAX_SPLINE_POINTS).contains(&pts.len())
|
||||
&& pts.iter().all(ok)
|
||||
&& (1..=7).contains(degree)
|
||||
&& (1..=solidcraft_geom::MAX_BEZIER_DEGREE as u8).contains(degree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::f64::consts::{FRAC_PI_2, PI};
|
||||
|
||||
use solidcraft_geom::Vec2;
|
||||
use solidcraft_geom::{Seg2, Vec2};
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -921,3 +921,49 @@ fn projected_boundary_keeps_its_pieces_when_only_stations_go() {
|
||||
assert!(sk.update_link(&j, &smaller).unwrap());
|
||||
assert_eq!(sk.link_curves(&j).len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_degree_nine_control_spline_stays_exact_and_closes_a_profile_exactly() {
|
||||
// A G4 blend curve is one degree-9 Bézier; a sketch keeps it as a control spline whose exact
|
||||
// piece is that Bézier, and profile measurement must keep using the curve, not its chords.
|
||||
let mut sk = Sketch::new();
|
||||
let bump =
|
||||
[v(0.0, 0.0), v(1.5, 4.0), v(3.0, 7.0), v(5.0, 9.5), v(7.5, 11.0), v(12.5, 11.0), v(15.0, 9.5), v(17.0, 7.0), v(18.5, 4.0), v(20.0, 0.0)];
|
||||
let pts: Vec<usize> = bump.iter().map(|q| sk.add_point(*q, None).unwrap()).collect();
|
||||
let s = sk.add_curve(CurveKind::Spline { pts: pts.clone(), control: true, degree: 9, periodic: false }, None).unwrap();
|
||||
let pieces = sk.exact_pieces(s);
|
||||
assert_eq!(pieces.len(), 1, "{pieces:?}");
|
||||
let Seg2::Bezier { n, p } = pieces[0] else { panic!("{:?}", pieces[0]) };
|
||||
assert_eq!(n, 9, "the highest degree a sketch piece can carry");
|
||||
for (i, q) in bump.iter().enumerate() {
|
||||
assert!(p[i].dist(*q) < 1e-12, "control {i}");
|
||||
}
|
||||
// Evaluation and the exact piece agree at the spline's real degree (a degree clamp would show
|
||||
// here), and a line's own exact piece still comes back as a line.
|
||||
let exact = pieces[0];
|
||||
for k in 0..=10 {
|
||||
let t = k as f64 / 10.0;
|
||||
assert!(crate::curves::spline_point(&bump, true, 9, t).dist(exact.point_at(t)) < 1e-9, "t={t}");
|
||||
}
|
||||
let l = sk.add_line(v(-5.0, 0.0), v(-2.0, 1.0), None, None, None).unwrap();
|
||||
assert!(matches!(sk.exact_pieces(l).as_slice(), [Seg2::Line { .. }]));
|
||||
// Close the loop: the region's area is the shoelace integral of the exact pieces, which a fine
|
||||
// sampling of the piece reproduces (a chord approximation would miss the bump's sagitta).
|
||||
sk.add_line(v(20.0, 0.0), v(0.0, 0.0), Some(pts[9]), Some(pts[0]), None).unwrap();
|
||||
let ps = find_profiles(&sk);
|
||||
assert_eq!(ps.len(), 1, "{ps:?}");
|
||||
let n = 20_000;
|
||||
let mut sampled = 0.0;
|
||||
for i in 0..n {
|
||||
let (t0, t1) = (i as f64 / n as f64, (i + 1) as f64 / n as f64);
|
||||
let (a, b) = (exact.point_at(t0), exact.point_at(t1));
|
||||
sampled += 0.5 * a.cross(b);
|
||||
}
|
||||
sampled -= 0.5 * v(20.0, 0.0).cross(v(0.0, 0.0));
|
||||
assert!((ps[0].area - sampled.abs()).abs() < 1e-5, "{} vs {sampled}", ps[0].area);
|
||||
assert!((ps[0].area - 100.0).abs() > 1e-3, "the region is not its chord: {}", ps[0].area);
|
||||
// A degree above the cap is still refused.
|
||||
let mut high: Vec<usize> = pts.clone();
|
||||
high.push(sk.add_point(v(21.0, -2.0), None).unwrap());
|
||||
assert!(sk.add_curve(CurveKind::Spline { pts: high, control: true, degree: 10, periodic: false }, None).is_err());
|
||||
}
|
||||
Reference in new issue
Block a user