feat(polyline): smooth per-segment taper for varying-width wide polylines

A wide polyline with per-vertex varying widths rendered at its widest edge
(uniform-width ones were already exact). Draw the true taper instead.

`WireModel.taper_widths` carries a per-point full band width. The wire
instance (native slim + web fat) gains per-endpoint `world_hw_a/b`, and both
wire shaders interpolate `mix(hw_a, hw_b, which_end)` so each segment's band
narrows/widens linearly — depth-tested, and the linetype dash still applies.

Alignment (widths must match the tessellated points) is solved by building
both together: a new `TruckObject::TaperedLines(points, widths)`, filled by
`common::tapered_band_points` (straight = endpoints, arc = 16 samples with the
width lerped along it). to_truck routes a varying-width polyline here —
LwPolyline before the PLINEGEN split, Polyline2D at its return — while a
uniform-width one keeps the constant-band Contour path unchanged. `world_width`
stays the widest edge as the PDF-stroke + zoom-floor fallback.

Blocks fall back to the constant-widest band (`LocalWire` carries no
taper_widths).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-20 16:36:06 +03:00
1 parent a8aece1909
commit b3016ca892
48 files changed
+322 -7

No files matched your search

+41
View File
@@ -392,6 +392,47 @@ pub(crate) fn thick_band_tube(
(fill_tris, lines)
}
/// Build a continuous WCS point list + a per-point FULL band width for a
/// tapered wide polyline, so the wire shader can interpolate each segment's two
/// endpoint widths. Each `verts` entry is `(location_xy, bulge_to_next,
/// start_width, end_width)` — the effective full widths at that vertex's segment
/// start and end (already resolved against the polyline's constant width). Arcs
/// are sampled in 16 steps with the width interpolated linearly along the arc.
/// A shared vertex is emitted once (carrying the previous segment's end width),
/// which is exact for the usual continuous taper.
pub(crate) fn tapered_band_points(
verts: &[([f64; 2], f64, f64, f64)],
is_closed: bool,
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
) -> (Vec<[f64; 3]>, Vec<f32>) {
let n = verts.len();
let seg_count = if is_closed { n } else { n.saturating_sub(1) };
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut widths: Vec<f32> = Vec::new();
let mut push = |x: f64, y: f64, w: f32| {
let (wx, wy, wz) = to_wcs(x, y);
pts.push([wx, wy, wz]);
widths.push(w);
};
for i in 0..seg_count {
let (p0, bulge, sw0, ew0) = verts[i];
let (p1, _, _, _) = verts[(i + 1) % n];
if i == 0 {
push(p0[0], p0[1], sw0 as f32);
}
if bulge.abs() < 1e-9 {
push(p1[0], p1[1], ew0 as f32);
} else if let Some(arc) = BulgeArc::from_bulge(p0, p1, bulge) {
for j in 1..=16usize {
let t = j as f64 / 16.0;
let s = arc.sample(t);
push(s[0], s[1], (sw0 + (ew0 - sw0) * t) as f32);
}
}
}
(pts, widths)
}
/// Compute the filled boundary polygon for one polyline segment.
/// For straight segments: a rectangle/trapezoid.
/// For arc segments: an arc band (outer arc + reversed inner arc).