feat(polyline): Polyline2D width band + tube, and block-instance shader bands

Extends the wide-polyline shader band to the two cases the first pass left on
the hatch pipeline.

Polyline2D:
- A flat wide Polyline2D now draws its band from the centre-line wire
  (`world_width`), same as LwPolyline (`polyline_band_width` handles both).
- A thickened wide Polyline2D extrudes a solid 3-D tube. `thick_wide_band`'s
  body moves to a shared `common::thick_band_tube(origin, fills, thickness,
  normal, to_wcs)`; LwPolyline delegates to it and Polyline2D calls it from
  its thickness path. Tube edges read black for both kinds.
- The model-space wide-band hatch loop is deleted (both kinds now wire/tube).

Blocks:
- Block-internal wide polylines draw the shader band too. `LocalWire` carries
  `world_width`; `emit_wire` scales it by the insert transform (average of the
  X/Y axis image lengths) so the band tracks a scaled insert, and the scaled
  width joins the batch `style_key`. `push_block_wide_fills` (the block hatch
  band) is removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-20 16:04:03 +03:00
commit a8aece1909
6 changed files with 161 additions and 219 deletions

View file

@ -334,6 +334,64 @@ pub(crate) fn wide_band_tris(origin: [f64; 2], fills: &[Vec<[f32; 2]>]) -> Vec<[
out
}
/// Extrude a wide-polyline band (from `wide_fills`) into a solid tube for a DXF
/// thickness (code 39): a vertical wall between every band-boundary point and
/// its `thickness`-along-`normal` copy, plus triangulated bottom and top caps.
/// Returns `(fill_tris, edge_lines)` as flat WCS f64 lists — the caller wraps
/// them in a `TruckEntity` (object = `Lines(edge_lines)`, `fill_tris`, and
/// `pick_tris = fill_tris`). Shared by LwPolyline and Polyline2D so both wide
/// polyline kinds extrude the same solid instead of just their centre-line.
///
/// `polyline_segment_fill` emits each band loop as the outer boundary forward
/// then the inner boundary back, so its two transition edges (`half-1 → half`
/// and `n-1 → 0`) are radial cap ends inside the band — no wall is drawn there.
pub(crate) fn thick_band_tube(
origin: [f64; 2],
fills: &[Vec<[f32; 2]>],
thickness: f64,
normal: (f64, f64, f64),
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
) -> (Vec<[f64; 3]>, Vec<[f64; 3]>) {
let (nx, ny, nz) = normal;
let t = thickness;
let off = |p: [f64; 3]| -> [f64; 3] { [p[0] + t * nx, p[1] + t * ny, p[2] + t * nz] };
let push_seg = |lines: &mut Vec<[f64; 3]>, a: [f64; 3], b: [f64; 3]| {
lines.push(a);
lines.push(b);
lines.push([f64::NAN; 3]);
};
let mut lines: Vec<[f64; 3]> = Vec::new();
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
for poly in fills {
let n = poly.len();
if n < 4 {
continue;
}
let half = n / 2;
let bot: Vec<[f64; 3]> = poly
.iter()
.map(|&[x, y]| {
let (wx, wy, wz) = to_wcs(origin[0] + x as f64, origin[1] + y as f64);
[wx, wy, wz]
})
.collect();
let top: Vec<[f64; 3]> = bot.iter().map(|&p| off(p)).collect();
for k in 0..n {
push_seg(&mut lines, bot[k], top[k]);
if k == half - 1 || k == n - 1 {
continue;
}
let kn = (k + 1) % n;
push_seg(&mut lines, bot[k], bot[kn]);
push_seg(&mut lines, top[k], top[kn]);
fill_tris.extend_from_slice(&[bot[k], bot[kn], top[kn], bot[k], top[kn], top[k]]);
}
fill_tris.extend(crate::entities::mesh::triangulate_planar(&bot));
fill_tris.extend(crate::entities::mesh::triangulate_planar(&top));
}
(fill_tris, lines)
}
/// 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).