forked from 1b-agent/opencadstudio-upstream
feat(tess): use angular sampling
Route curve, hatch, sweep, loft, surface, edge, and isoline tessellation through the shared kernel policy.
This commit is contained in:
1 parent
f607422c9d
commit
96be8214f9
21 files changed
+158
-767
No files matched your search
+8
-189
@@ -1398,195 +1398,14 @@ impl Scene {
|
||||
|
||||
let mut edge_polys: Vec<Vec<[f64; 2]>> = Vec::new();
|
||||
for edge in &path.edges {
|
||||
match edge {
|
||||
BoundaryEdge::Polyline(poly) => {
|
||||
let verts = &poly.vertices;
|
||||
let count = verts.len();
|
||||
if count == 0 {
|
||||
continue;
|
||||
}
|
||||
let seg_count = if poly.is_closed {
|
||||
count
|
||||
} else {
|
||||
count.saturating_sub(1)
|
||||
};
|
||||
for i in 0..seg_count {
|
||||
let v0 = &verts[i];
|
||||
let v1 = &verts[(i + 1) % count];
|
||||
let bulge = v0.z;
|
||||
// Tess in f64 to preserve ~1 cm precision at
|
||||
// UTM-scale WCS (the f32 path used to produce
|
||||
// visibly wavy hatch arcs at 1e5+ magnitude).
|
||||
let arc = if bulge.abs() < 1e-9 {
|
||||
None
|
||||
} else {
|
||||
crate::entities::common::BulgeArc::from_bulge(
|
||||
[v0.x, v0.y],
|
||||
[v1.x, v1.y],
|
||||
bulge,
|
||||
)
|
||||
};
|
||||
let Some(arc) = arc else {
|
||||
boundary.push(to_xy(v0.x, v0.y));
|
||||
continue;
|
||||
};
|
||||
let segs = convert::tess_util::arc_segments(
|
||||
arc.radius,
|
||||
arc.sweep.abs(),
|
||||
convert::tess_util::fill_chord_tol(arc.radius),
|
||||
);
|
||||
for j in 0..segs {
|
||||
let s = arc.sample(j as f64 / segs as f64);
|
||||
boundary.push(to_xy(s[0], s[1]));
|
||||
}
|
||||
}
|
||||
if poly.is_closed {
|
||||
if let Some(&first) = boundary.get(path_start) {
|
||||
boundary.push(first);
|
||||
}
|
||||
}
|
||||
}
|
||||
BoundaryEdge::Line(line) => {
|
||||
edge_polys.push(vec![
|
||||
to_xy(line.start.x, line.start.y),
|
||||
to_xy(line.end.x, line.end.y),
|
||||
]);
|
||||
}
|
||||
BoundaryEdge::CircularArc(arc) => {
|
||||
let (sa, span) = convert::tess_util::arc_signed_span(
|
||||
arc.start_angle,
|
||||
arc.end_angle,
|
||||
arc.counter_clockwise,
|
||||
);
|
||||
let segs = convert::tess_util::arc_segments(
|
||||
arc.radius,
|
||||
span.abs(),
|
||||
convert::tess_util::fill_chord_tol(arc.radius),
|
||||
);
|
||||
let mut pts = Vec::with_capacity(segs as usize + 1);
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
pts.push(to_xy(
|
||||
arc.center.x + arc.radius * t.cos(),
|
||||
arc.center.y + arc.radius * t.sin(),
|
||||
));
|
||||
}
|
||||
edge_polys.push(pts);
|
||||
}
|
||||
BoundaryEdge::EllipticArc(ell) => {
|
||||
let r_maj = (ell.major_axis_endpoint.x * ell.major_axis_endpoint.x
|
||||
+ ell.major_axis_endpoint.y * ell.major_axis_endpoint.y)
|
||||
.sqrt();
|
||||
let r_min = r_maj * ell.minor_axis_ratio;
|
||||
let rot = ell
|
||||
.major_axis_endpoint
|
||||
.y
|
||||
.atan2(ell.major_axis_endpoint.x);
|
||||
let (sa, span) = convert::tess_util::arc_signed_span(
|
||||
ell.start_angle,
|
||||
ell.end_angle,
|
||||
ell.counter_clockwise,
|
||||
);
|
||||
let segs = convert::tess_util::arc_segments(
|
||||
r_maj,
|
||||
span.abs(),
|
||||
convert::tess_util::fill_chord_tol(r_maj),
|
||||
);
|
||||
let (cr, sr) = (rot.cos(), rot.sin());
|
||||
let mut pts = Vec::with_capacity(segs as usize + 1);
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
let lx = r_maj * t.cos();
|
||||
let ly = r_min * t.sin();
|
||||
pts.push(to_xy(
|
||||
ell.center.x + lx * cr - ly * sr,
|
||||
ell.center.y + lx * sr + ly * cr,
|
||||
));
|
||||
}
|
||||
edge_polys.push(pts);
|
||||
}
|
||||
BoundaryEdge::Spline(spline) => {
|
||||
// DXF spline control_points pack (x, y, weight) into
|
||||
// a Vector3 — the z field is the rational weight, NOT
|
||||
// a Z coordinate. The legacy code dropped weight and
|
||||
// sampled with a fixed 16 segments; both bugs
|
||||
// produced visibly wrong fill regions for spline-
|
||||
// bounded hatches (especially block-internal ones,
|
||||
// where boundaries are often spline curves with
|
||||
// rational weights and short cubic segments).
|
||||
//
|
||||
// The kernel's plane NURBS holds the rational and
|
||||
// the polynomial case alike — weights absent means
|
||||
// polynomial — and tessellates to a chord tolerance,
|
||||
// the same one the fill polygon uses for arcs.
|
||||
let degree = spline.degree.max(0) as usize;
|
||||
|
||||
// Rough chord-tolerance: 0.1% of the control-poly
|
||||
// diagonal so adaptive sampling produces enough
|
||||
// points to follow the curve without exploding on
|
||||
// huge splines.
|
||||
let (mut sp_min_x, mut sp_min_y) = (f64::INFINITY, f64::INFINITY);
|
||||
let (mut sp_max_x, mut sp_max_y) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
|
||||
for cp in &spline.control_points {
|
||||
sp_min_x = sp_min_x.min(cp.x);
|
||||
sp_min_y = sp_min_y.min(cp.y);
|
||||
sp_max_x = sp_max_x.max(cp.x);
|
||||
sp_max_y = sp_max_y.max(cp.y);
|
||||
}
|
||||
let diag = ((sp_max_x - sp_min_x).powi(2)
|
||||
+ (sp_max_y - sp_min_y).powi(2))
|
||||
.sqrt();
|
||||
let tol = convert::tess_util::fill_chord_tol(diag.max(1.0));
|
||||
|
||||
let mut epts: Vec<[f64; 2]> = Vec::new();
|
||||
let mut sampled = false;
|
||||
let points: Vec<[f64; 2]> = spline
|
||||
.control_points
|
||||
.iter()
|
||||
.map(|p| [p.x, p.y])
|
||||
.collect();
|
||||
let weights = spline.rational.then(|| {
|
||||
spline
|
||||
.control_points
|
||||
.iter()
|
||||
.map(|p| if p.z.abs() > 1e-12 { p.z } else { 1.0 })
|
||||
.collect()
|
||||
});
|
||||
if let Some(curve) = acadrust::kernel::geom2d::NurbsCurve::new(
|
||||
degree,
|
||||
points,
|
||||
spline.knots.clone(),
|
||||
weights,
|
||||
) {
|
||||
for point in
|
||||
acadrust::kernel::geom2d::Curve::Nurbs(curve).tessellate_within(tol)
|
||||
{
|
||||
epts.push(to_xy(point[0], point[1]));
|
||||
}
|
||||
sampled = epts.len() >= 2;
|
||||
}
|
||||
if !sampled {
|
||||
// Fallback: prefer fit_points (which lie on the
|
||||
// curve) over control_points (which usually
|
||||
// don't). A control-point polyline would draw
|
||||
// the convex-hull silhouette — visibly wrong.
|
||||
let pts: &[_] = if !spline.fit_points.is_empty() {
|
||||
&spline.fit_points
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
if !pts.is_empty() {
|
||||
for p in pts {
|
||||
epts.push(to_xy(p.x, p.y));
|
||||
}
|
||||
} else {
|
||||
for cp in &spline.control_points {
|
||||
epts.push(to_xy(cp.x, cp.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
edge_polys.push(epts);
|
||||
}
|
||||
if let Some(curve) = crate::entities::hatch::edge_curve(edge) {
|
||||
edge_polys.push(
|
||||
curve
|
||||
.tessellate_angle(acadrust::kernel::tessellation::DEFAULT_ANGLE)
|
||||
.into_iter()
|
||||
.map(|point| to_xy(point[0], point[1]))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
boundary.extend(chain_path_edges(edge_polys));
|
||||
|
||||
Reference in new issue
Block a user