fix(polyline): honor global width and fill mode
This commit is contained in:
parent
7215faae44
commit
8c56c03387
6 changed files with 107 additions and 7 deletions
|
|
@ -964,7 +964,16 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
|||
};
|
||||
match field {
|
||||
"elevation" => pline.elevation = v,
|
||||
"global_width" => pline.constant_width = v,
|
||||
"global_width" if v.is_finite() && v >= 0.0 => {
|
||||
pline.constant_width = v;
|
||||
// Global width replaces every segment override. Keeping old
|
||||
// start/end values would make this editable row appear to work
|
||||
// while the renderer continued using the stale per-segment data.
|
||||
for vertex in &mut pline.vertices {
|
||||
vertex.start_width = 0.0;
|
||||
vertex.end_width = 0.0;
|
||||
}
|
||||
}
|
||||
"vertex_x" => {
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.location.x = v;
|
||||
|
|
@ -975,12 +984,28 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
|||
vtx.location.y = v;
|
||||
}
|
||||
}
|
||||
"start_width" => {
|
||||
"start_width" if v.is_finite() && v >= 0.0 => {
|
||||
if pline.constant_width > 0.0 {
|
||||
let width = pline.constant_width;
|
||||
for vertex in &mut pline.vertices {
|
||||
vertex.start_width = width;
|
||||
vertex.end_width = width;
|
||||
}
|
||||
pline.constant_width = 0.0;
|
||||
}
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.start_width = v;
|
||||
}
|
||||
}
|
||||
"end_width" => {
|
||||
"end_width" if v.is_finite() && v >= 0.0 => {
|
||||
if pline.constant_width > 0.0 {
|
||||
let width = pline.constant_width;
|
||||
for vertex in &mut pline.vertices {
|
||||
vertex.start_width = width;
|
||||
vertex.end_width = width;
|
||||
}
|
||||
pline.constant_width = 0.0;
|
||||
}
|
||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||
vtx.end_width = v;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ pub struct Uniforms {
|
|||
/// zooming inside MSPACE changes one scalar instead of re-tessellating and
|
||||
/// re-uploading every dashed wire.
|
||||
pub linetype_scale: f32,
|
||||
/// Pads the struct to the uniform alignment required by wgpu.
|
||||
/// FILLMODE toggle. Wire shaders use this slot to draw wide polylines as
|
||||
/// boundary outlines when fills are disabled.
|
||||
pub _pad: f32,
|
||||
|
||||
// ── Relative-to-eye (double-single) additions ───────────────────────────
|
||||
|
|
|
|||
|
|
@ -3527,6 +3527,7 @@ impl Scene {
|
|||
};
|
||||
let mut uniforms =
|
||||
Uniforms::new(&inst.camera, full_bounds, self.document.header.lineweight_display);
|
||||
uniforms._pad = if self.document.header.fill_mode { 1.0 } else { 0.0 };
|
||||
if self.document.header.paper_space_linetype_scaling
|
||||
&& !inst.paper_sheet
|
||||
&& inst.tile_idx.is_none()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ struct Uniforms {
|
|||
flat_shade: f32,
|
||||
transparency_enable: f32,
|
||||
linetype_scale: f32,
|
||||
_pad: f32,
|
||||
fillmode_enable: f32,
|
||||
view_rot: mat4x4<f32>,
|
||||
eye_high: vec3<f32>,
|
||||
_pad_eh: f32,
|
||||
|
|
@ -58,6 +58,7 @@ struct VertexOut {
|
|||
@location(7) @interpolate(flat) align_total: f32,
|
||||
@location(8) cap: vec2<f32>,
|
||||
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
|
||||
@location(10) @interpolate(flat) world_band: f32,
|
||||
}
|
||||
|
||||
fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 {
|
||||
|
|
@ -175,6 +176,11 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut
|
|||
half_width * side,
|
||||
);
|
||||
out.cap_ends = vec3<f32>(segment_length, half_width_a, half_width_b);
|
||||
out.world_band = select(
|
||||
0.0,
|
||||
1.0,
|
||||
wire_const.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -244,10 +250,28 @@ fn clipped_cap(cap: vec2<f32>, ends: vec3<f32>) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn band_interior_clipped(in: VertexOut) -> bool {
|
||||
if u.fillmode_enable > 0.5 || in.world_band < 0.5 {
|
||||
return false;
|
||||
}
|
||||
if in.cap.x < 0.0 || in.cap.x > in.cap_ends.x {
|
||||
return true;
|
||||
}
|
||||
var along = 0.0;
|
||||
if in.cap_ends.x > 1e-4 {
|
||||
along = clamp(in.cap.x / in.cap_ends.x, 0.0, 1.0);
|
||||
}
|
||||
let half_width = mix(in.cap_ends.y, in.cap_ends.z, along);
|
||||
return abs(in.cap.y) < max(half_width - 1.0, 0.0);
|
||||
}
|
||||
|
||||
fn visible_fragment(in: VertexOut) -> bool {
|
||||
if clipped_cap(in.cap, in.cap_ends) {
|
||||
return false;
|
||||
}
|
||||
if band_interior_clipped(in) {
|
||||
return false;
|
||||
}
|
||||
if in.pattern_length > 0.0 && in.min_elem >= u.world_per_pixel {
|
||||
return in_dash(
|
||||
in.distance,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ struct Uniforms {
|
|||
// every line opaque.
|
||||
transparency_enable: f32,
|
||||
linetype_scale: f32,
|
||||
_pad: f32,
|
||||
fillmode_enable: f32,
|
||||
// ── Relative-to-eye (double-single) ──────────────────────────────────
|
||||
// view_rot is the rotation-only view-projection; vertices subtract the eye
|
||||
// (eye_high + eye_low, two f32 emulating f64) before transforming, so the
|
||||
|
|
@ -94,6 +94,7 @@ struct VertexOut {
|
|||
@location(8) cap: vec2<f32>,
|
||||
// (segment pixel length, end half-width at A, end half-width at B).
|
||||
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
|
||||
@location(10) @interpolate(flat) world_band: f32,
|
||||
}
|
||||
|
||||
// Half-width of one segment end: a tapered band's own end width wins, then a
|
||||
|
|
@ -215,6 +216,7 @@ fn marker_relative(position_high: vec3<f32>, position_low: vec3<f32>, instance:
|
|||
+ ext * hw * u.world_per_pixel;
|
||||
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||
out.cap_ends = vec3<f32>(seg_len, hw_a, hw_b);
|
||||
out.world_band = select(0.0, 1.0, in.misc.w > 0.0 || in.taper.x > 0.0 || in.taper.y > 0.0);
|
||||
out.pattern_length = in.dists.w * lt_scale;
|
||||
out.pat0 = in.pat0 * lt_scale;
|
||||
out.pat1 = in.pat1 * lt_scale;
|
||||
|
|
@ -287,6 +289,24 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn band_interior_clipped(in: VertexOut) -> bool {
|
||||
if u.fillmode_enable > 0.5 || in.world_band < 0.5 {
|
||||
return false;
|
||||
}
|
||||
// Wide-polyline fill off: retain a one-pixel line at both band edges.
|
||||
// Segment cap overhangs are discarded; adjacent sampled segments meet at
|
||||
// their shared endpoint without drawing an interior seam across the band.
|
||||
if in.cap.x < 0.0 || in.cap.x > in.cap_ends.x {
|
||||
return true;
|
||||
}
|
||||
var along = 0.0;
|
||||
if in.cap_ends.x > 1e-4 {
|
||||
along = clamp(in.cap.x / in.cap_ends.x, 0.0, 1.0);
|
||||
}
|
||||
let half_width = mix(in.cap_ends.y, in.cap_ends.z, along);
|
||||
return abs(in.cap.y) < max(half_width - 1.0, 0.0);
|
||||
}
|
||||
|
||||
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
// Negative pattern length is the persistent-arena tombstone sentinel.
|
||||
// Discard before cap/alpha work so deleted slabs cannot write color/depth.
|
||||
|
|
@ -296,6 +316,9 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
|
|||
if cap_clipped(in.cap, in.cap_ends) {
|
||||
discard;
|
||||
}
|
||||
if band_interior_clipped(in) {
|
||||
discard;
|
||||
}
|
||||
if in.pattern_length > 0.0 {
|
||||
// LOD: once the pattern's smallest feature drops below ~1 px
|
||||
// on screen, dash gaps alias / shimmer (or vanish completely)
|
||||
|
|
@ -323,6 +346,9 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
|
|||
if cap_clipped(in.cap, in.cap_ends) {
|
||||
discard;
|
||||
}
|
||||
if band_interior_clipped(in) {
|
||||
discard;
|
||||
}
|
||||
if in.pattern_length > 0.0 {
|
||||
if in.min_elem >= u.world_per_pixel {
|
||||
if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct Uniforms {
|
|||
flat_shade: f32,
|
||||
transparency_enable: f32,
|
||||
linetype_scale: f32,
|
||||
_pad: f32,
|
||||
fillmode_enable: f32,
|
||||
view_rot: mat4x4<f32>,
|
||||
eye_high: vec3<f32>,
|
||||
_pad_eh: f32,
|
||||
|
|
@ -71,6 +71,7 @@ struct VertexOut {
|
|||
@location(8) cap: vec2<f32>,
|
||||
// (segment pixel length, end half-width at A, end half-width at B).
|
||||
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
|
||||
@location(10) @interpolate(flat) world_band: f32,
|
||||
}
|
||||
|
||||
// Half-width of one segment end: a tapered band's own end width wins, then a
|
||||
|
|
@ -181,6 +182,7 @@ fn marker_relative(position_high: vec3<f32>, position_low: vec3<f32>, c: WireCon
|
|||
+ ext * hw * u.world_per_pixel;
|
||||
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||
out.cap_ends = vec3<f32>(seg_len, hw_a, hw_b);
|
||||
out.world_band = select(0.0, 1.0, c.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0);
|
||||
out.pattern_length = c.pattern_length * lt_scale;
|
||||
out.pat0 = c.pat0 * lt_scale;
|
||||
out.pat1 = c.pat1 * lt_scale;
|
||||
|
|
@ -244,6 +246,21 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn band_interior_clipped(in: VertexOut) -> bool {
|
||||
if u.fillmode_enable > 0.5 || in.world_band < 0.5 {
|
||||
return false;
|
||||
}
|
||||
if in.cap.x < 0.0 || in.cap.x > in.cap_ends.x {
|
||||
return true;
|
||||
}
|
||||
var along = 0.0;
|
||||
if in.cap_ends.x > 1e-4 {
|
||||
along = clamp(in.cap.x / in.cap_ends.x, 0.0, 1.0);
|
||||
}
|
||||
let half_width = mix(in.cap_ends.y, in.cap_ends.z, along);
|
||||
return abs(in.cap.y) < max(half_width - 1.0, 0.0);
|
||||
}
|
||||
|
||||
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
// Negative pattern length is the persistent-arena tombstone sentinel.
|
||||
// Discard before cap/alpha work so deleted slabs cannot write color/depth.
|
||||
|
|
@ -253,6 +270,9 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
|
|||
if cap_clipped(in.cap, in.cap_ends) {
|
||||
discard;
|
||||
}
|
||||
if band_interior_clipped(in) {
|
||||
discard;
|
||||
}
|
||||
if in.pattern_length > 0.0 {
|
||||
if in.min_elem >= u.world_per_pixel {
|
||||
if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) {
|
||||
|
|
@ -274,6 +294,9 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
|
|||
if cap_clipped(in.cap, in.cap_ends) {
|
||||
discard;
|
||||
}
|
||||
if band_interior_clipped(in) {
|
||||
discard;
|
||||
}
|
||||
if in.pattern_length > 0.0 {
|
||||
if in.min_elem >= u.world_per_pixel {
|
||||
if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue