feat(lwpolyline): render the wide-polyline band via the wire shader

A wide polyline's flat band was drawn by the hatch pipeline: a solid fill
with only draw-order depth. It ignored the entity linetype (the width
"crushed" the dash pattern) and showed through 3-D geometry in a tilted view.

Draw the band from the polyline's own centre-line wire instead. A new
`WireModel.world_width` (drawing units) makes the wire vertex shader expand
the quad by `world_half_width / world_per_pixel` (clamped to a hairline), so
the band is sized in world units and tracks zoom. Because it is now a wire:

- the GPU dash shader dashes the band by arc-length — the linetype applies;
- it is depth-tested at the polyline elevation — no draw-order see-through;
- no CPU band triangles or per-dash geometry are generated.

`world_half_width` rides in `WireConst` (native, replacing a pad) and the web
`WireInstance` (loc 14); both wire shaders branch on it. The tessellator sets
`world_width` from the LwPolyline width on the Contour / SegmentedLines arms,
and the model-space fill loop no longer emits a hatch band for any LwPolyline.
Pick still uses the band polygon (`pick_tris`).

Blocks keep the hatch band: `LocalWire` carries no `world_width` (finalize
zeroes it), so a scaled insert still scales its band correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-20 14:50:27 +03:00
1 parent 3d37e1f8d8
commit bd1a5affd7
44 files changed
+158 -17

No files matched your search

+3
View File
@@ -650,6 +650,7 @@ pub(crate) fn tessellate_entity(
(ins.insert_point.z) as f32,
);
let marker = WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -1029,6 +1030,7 @@ fn lod_stub_wire(
// LOD boundary. #19.
let stored_color = if selected { WireModel::SELECTED } else { color };
WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -1115,6 +1117,7 @@ fn lod_stub_wire_3d(
}
let stored_color = if selected { WireModel::SELECTED } else { color };
WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
+45 -2
View File
@@ -370,6 +370,7 @@ pub fn tessellate(
None
};
elem_wires.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -660,6 +661,7 @@ pub fn tessellate(
ftl.push(lo);
}
wires.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -697,6 +699,7 @@ pub fn tessellate(
fpl.push(lo);
}
wires.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -741,6 +744,7 @@ pub fn tessellate(
low.push(ll);
}
wires.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -766,6 +770,7 @@ pub fn tessellate(
});
}
wires.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -821,6 +826,7 @@ pub fn tessellate(
(Vec::new(), Vec::new(), Vec::new())
};
out.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -858,6 +864,7 @@ pub fn tessellate(
(Vec::new(), Vec::new(), Vec::new())
};
out.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -892,6 +899,7 @@ pub fn tessellate(
// are empty → the early-return path above).
if !sdf_verts.is_empty() {
out.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -919,6 +927,7 @@ pub fn tessellate(
if out.is_empty() {
out.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -968,6 +977,7 @@ pub fn tessellate(
.map(|[kx, ky, kz]| [kx, ky, kz])
.collect();
return vec![WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -1016,6 +1026,7 @@ pub fn tessellate(
.map(|[x, y, z]| [x, y, z])
.collect();
return vec![WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -1052,9 +1063,12 @@ pub fn tessellate(
.into_iter()
.map(|[x, y, z]| [x, y, z])
.collect();
// A wide polyline arrives here: its band is pick geometry.
// A wide polyline arrives here: the shader expands this
// centre-line to `world_width` so the band IS the wire (the
// linetype dashes it); the band also backs pick.
let (pick_tris, pick_tris_low) = points_to_ds(te.pick_tris);
return vec![WireModel {
world_width: lwpolyline_band_width(entity),
fill_is_3d: false,
pick_tris,
pick_tris_low,
@@ -1140,6 +1154,7 @@ pub fn tessellate(
(Vec::new(), Vec::new(), Vec::new())
};
out.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris,
pick_tris_low,
@@ -1176,6 +1191,7 @@ pub fn tessellate(
(Vec::new(), Vec::new(), Vec::new())
};
out.push(WireModel {
world_width: 0.0,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
dash_from_start: false,
@@ -1203,6 +1219,7 @@ pub fn tessellate(
if out.is_empty() {
out.push(WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -1239,9 +1256,11 @@ pub fn tessellate(
.into_iter()
.map(|[x, y, z]| [x, y, z])
.collect();
// A wide polyline with PLINEGEN=0 arrives here.
// A wide polyline with PLINEGEN=0 arrives here: same shader-band
// treatment as the Contour arm, restarting the dash per segment.
let (pick_tris, pick_tris_low) = points_to_ds(te.pick_tris);
return vec![WireModel {
world_width: lwpolyline_band_width(entity),
fill_is_3d: false,
pick_tris,
pick_tris_low,
@@ -1310,6 +1329,7 @@ pub fn tessellate(
let snap_pts: Vec<(glam::DVec3, SnapHint)> =
snap_pts.into_iter().map(|(p, h)| (p.as_dvec3(), h)).collect();
vec![WireModel {
world_width: 0.0,
fill_is_3d: false,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
@@ -1489,6 +1509,29 @@ pub(crate) fn entity_z(entity: &EntityType) -> f32 {
}
}
/// Band width (drawing units) of a wide LwPolyline whose flat band is drawn by
/// expanding its centre-line wire in the shader. `0.0` = a zero-width polyline
/// or a non-LwPolyline (a normal screen-pixel wire). A thickness-extruded
/// polyline is excluded: its band becomes a 3-D tube (`thick_wide_band`), not a
/// flat shader band. A tapering polyline (per-vertex start/end widths) collapses
/// to its widest edge — the shader carries one width per wire.
fn lwpolyline_band_width(entity: &EntityType) -> f32 {
match entity {
EntityType::LwPolyline(p) if p.thickness.abs() <= 1e-10 => {
let mut w = p.constant_width;
for v in &p.vertices {
w = w.max(v.start_width).max(v.end_width);
}
if w > 1e-9 {
w as f32
} else {
0.0
}
}
_ => 0.0,
}
}
// ── Fallback geometry (Viewport, Insert, Hatch outline, Ole2Frame) ───────
//
// Per-entity blocks have moved to their respective `entities/*.rs` files