From 76e1610dcc1ee72cb71ef15dd3eb6ee233c9bffa Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Wed, 9 Sep 2026 13:44:57 +0100 Subject: [PATCH] fix(render): render wide polyline segments as flat 2D rectangles instead of rounded pill shapes --- src/scene/pipeline/wire_gpu.rs | 26 ++++++++++++++++++++++++++ src/shaders/block_wire.wgsl | 3 ++- src/shaders/block_wire_storage.wgsl | 3 ++- src/shaders/wire.wgsl | 6 +++++- src/shaders/wire_indexed.wgsl | 6 +++++- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index e12578375..0e2884af4 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -1464,4 +1464,30 @@ mod block_wire_storage_tests { }; assert_eq!(names(&packed), names(&storage)); } + + #[test] + fn wire_shader_validates_with_naga() { + let source = include_str!("../../shaders/wire.wgsl"); + let module = naga::front::wgsl::parse_str(source).expect("wire.wgsl parses cleanly"); + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::all(), + ); + validator + .validate(&module) + .expect("wire.wgsl validates cleanly with naga"); + } + + #[test] + fn wire_indexed_shader_validates_with_naga() { + let source = include_str!("../../shaders/wire_indexed.wgsl"); + let module = naga::front::wgsl::parse_str(source).expect("wire_indexed.wgsl parses cleanly"); + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::all(), + ); + validator + .validate(&module) + .expect("wire_indexed.wgsl validates cleanly with naga"); + } } diff --git a/src/shaders/block_wire.wgsl b/src/shaders/block_wire.wgsl index a8500bddc..3847bbaa5 100644 --- a/src/shaders/block_wire.wgsl +++ b/src/shaders/block_wire.wgsl @@ -144,7 +144,8 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut wire_const.half_width, ); let half_width = mix(half_width_a, half_width_b, which_end); - let extension = which_end * 2.0 - 1.0; + let is_world = wire_const.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0; + let extension = select(which_end * 2.0 - 1.0, 0.0, is_world); let offset_px = perpendicular * half_width * side + direction * half_width * extension; let ndc_offset = offset_px / (u.viewport_size * 0.5); diff --git a/src/shaders/block_wire_storage.wgsl b/src/shaders/block_wire_storage.wgsl index 98cda4857..a11ac646d 100644 --- a/src/shaders/block_wire_storage.wgsl +++ b/src/shaders/block_wire_storage.wgsl @@ -175,7 +175,8 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut wire_const.half_width, ); let half_width = mix(half_width_a, half_width_b, which_end); - let extension = which_end * 2.0 - 1.0; + let is_world = wire_const.world_half_width > 0.0 || seg_taper_ratio.x > 0.0 || seg_taper_ratio.y > 0.0; + let extension = select(which_end * 2.0 - 1.0, 0.0, is_world); let offset_px = perpendicular * half_width * side + direction * half_width * extension; let ndc_offset = offset_px / (u.viewport_size * 0.5); diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index c41abd617..b89067aea 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -196,7 +196,11 @@ fn marker_relative(position_high: vec3, position_low: vec3, instance: // fragment stage round the overhang off: adjoining segments then meet in // overlapping round joints, closing the wedge gaps a perpendicular-only // expansion leaves on the outside of corners and along tessellated arcs. - let ext = which_end * 2.0 - 1.0; // -1 at the A end, +1 at the B end + // Wide polylines have physical world-unit width (in.misc.w or in.taper > 0); + // in CAD, wide polyline segments are flat/square-ended 2D rectangles (or + // trapezoids), not rounded pill shapes. + let is_world = in.misc.w > 0.0 || in.taper.x > 0.0 || in.taper.y > 0.0; + let ext = select(which_end * 2.0 - 1.0, 0.0, is_world); let offset_px = perp * hw * side + dir * hw * ext; let ndc_offset = offset_px / (u.viewport_size * 0.5); let final_clip = clip_pos + vec4(ndc_offset * clip_pos.w, 0.0, 0.0); diff --git a/src/shaders/wire_indexed.wgsl b/src/shaders/wire_indexed.wgsl index c62b3e189..e474c8088 100644 --- a/src/shaders/wire_indexed.wgsl +++ b/src/shaders/wire_indexed.wgsl @@ -165,7 +165,11 @@ fn marker_relative(position_high: vec3, position_low: vec3, c: WireCon // fragment stage round the overhang off: adjoining segments then meet in // overlapping round joints, closing the wedge gaps a perpendicular-only // expansion leaves on the outside of corners and along tessellated arcs. - let ext = which_end * 2.0 - 1.0; // -1 at the A end, +1 at the B end + // Wide polylines have physical world-unit width (world_half_width or taper > 0); + // in CAD, wide polyline segments are flat/square-ended 2D rectangles (or + // trapezoids), not rounded pill shapes. + let is_world = c.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0; + let ext = select(which_end * 2.0 - 1.0, 0.0, is_world); let offset_px = perp * hw * side + dir * hw * ext; let ndc_offset = offset_px / (u.viewport_size * 0.5); let final_clip = clip_pos + vec4(ndc_offset * clip_pos.w, 0.0, 0.0);