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 {
|
match field {
|
||||||
"elevation" => pline.elevation = v,
|
"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" => {
|
"vertex_x" => {
|
||||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||||
vtx.location.x = v;
|
vtx.location.x = v;
|
||||||
|
|
@ -975,12 +984,28 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||||
vtx.location.y = v;
|
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) {
|
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||||
vtx.start_width = v;
|
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) {
|
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||||
vtx.end_width = v;
|
vtx.end_width = v;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ pub struct Uniforms {
|
||||||
/// zooming inside MSPACE changes one scalar instead of re-tessellating and
|
/// zooming inside MSPACE changes one scalar instead of re-tessellating and
|
||||||
/// re-uploading every dashed wire.
|
/// re-uploading every dashed wire.
|
||||||
pub linetype_scale: f32,
|
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,
|
pub _pad: f32,
|
||||||
|
|
||||||
// ── Relative-to-eye (double-single) additions ───────────────────────────
|
// ── Relative-to-eye (double-single) additions ───────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -3527,6 +3527,7 @@ impl Scene {
|
||||||
};
|
};
|
||||||
let mut uniforms =
|
let mut uniforms =
|
||||||
Uniforms::new(&inst.camera, full_bounds, self.document.header.lineweight_display);
|
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
|
if self.document.header.paper_space_linetype_scaling
|
||||||
&& !inst.paper_sheet
|
&& !inst.paper_sheet
|
||||||
&& inst.tile_idx.is_none()
|
&& inst.tile_idx.is_none()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ struct Uniforms {
|
||||||
flat_shade: f32,
|
flat_shade: f32,
|
||||||
transparency_enable: f32,
|
transparency_enable: f32,
|
||||||
linetype_scale: f32,
|
linetype_scale: f32,
|
||||||
_pad: f32,
|
fillmode_enable: f32,
|
||||||
view_rot: mat4x4<f32>,
|
view_rot: mat4x4<f32>,
|
||||||
eye_high: vec3<f32>,
|
eye_high: vec3<f32>,
|
||||||
_pad_eh: f32,
|
_pad_eh: f32,
|
||||||
|
|
@ -58,6 +58,7 @@ struct VertexOut {
|
||||||
@location(7) @interpolate(flat) align_total: f32,
|
@location(7) @interpolate(flat) align_total: f32,
|
||||||
@location(8) cap: vec2<f32>,
|
@location(8) cap: vec2<f32>,
|
||||||
@location(9) @interpolate(flat) cap_ends: vec3<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 {
|
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,
|
half_width * side,
|
||||||
);
|
);
|
||||||
out.cap_ends = vec3<f32>(segment_length, half_width_a, half_width_b);
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,10 +250,28 @@ fn clipped_cap(cap: vec2<f32>, ends: vec3<f32>) -> bool {
|
||||||
return false;
|
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 {
|
fn visible_fragment(in: VertexOut) -> bool {
|
||||||
if clipped_cap(in.cap, in.cap_ends) {
|
if clipped_cap(in.cap, in.cap_ends) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if band_interior_clipped(in) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if in.pattern_length > 0.0 && in.min_elem >= u.world_per_pixel {
|
if in.pattern_length > 0.0 && in.min_elem >= u.world_per_pixel {
|
||||||
return in_dash(
|
return in_dash(
|
||||||
in.distance,
|
in.distance,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ struct Uniforms {
|
||||||
// every line opaque.
|
// every line opaque.
|
||||||
transparency_enable: f32,
|
transparency_enable: f32,
|
||||||
linetype_scale: f32,
|
linetype_scale: f32,
|
||||||
_pad: f32,
|
fillmode_enable: f32,
|
||||||
// ── Relative-to-eye (double-single) ──────────────────────────────────
|
// ── Relative-to-eye (double-single) ──────────────────────────────────
|
||||||
// view_rot is the rotation-only view-projection; vertices subtract the eye
|
// view_rot is the rotation-only view-projection; vertices subtract the eye
|
||||||
// (eye_high + eye_low, two f32 emulating f64) before transforming, so the
|
// (eye_high + eye_low, two f32 emulating f64) before transforming, so the
|
||||||
|
|
@ -94,6 +94,7 @@ struct VertexOut {
|
||||||
@location(8) cap: vec2<f32>,
|
@location(8) cap: vec2<f32>,
|
||||||
// (segment pixel length, end half-width at A, end half-width at B).
|
// (segment pixel length, end half-width at A, end half-width at B).
|
||||||
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
|
@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
|
// 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;
|
+ ext * hw * u.world_per_pixel;
|
||||||
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||||
out.cap_ends = vec3<f32>(seg_len, hw_a, hw_b);
|
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.pattern_length = in.dists.w * lt_scale;
|
||||||
out.pat0 = in.pat0 * lt_scale;
|
out.pat0 = in.pat0 * lt_scale;
|
||||||
out.pat1 = in.pat1 * 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;
|
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> {
|
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||||
// Negative pattern length is the persistent-arena tombstone sentinel.
|
// Negative pattern length is the persistent-arena tombstone sentinel.
|
||||||
// Discard before cap/alpha work so deleted slabs cannot write color/depth.
|
// 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) {
|
if cap_clipped(in.cap, in.cap_ends) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
if band_interior_clipped(in) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
if in.pattern_length > 0.0 {
|
if in.pattern_length > 0.0 {
|
||||||
// LOD: once the pattern's smallest feature drops below ~1 px
|
// LOD: once the pattern's smallest feature drops below ~1 px
|
||||||
// on screen, dash gaps alias / shimmer (or vanish completely)
|
// 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) {
|
if cap_clipped(in.cap, in.cap_ends) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
if band_interior_clipped(in) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
if in.pattern_length > 0.0 {
|
if in.pattern_length > 0.0 {
|
||||||
if in.min_elem >= u.world_per_pixel {
|
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) {
|
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,
|
flat_shade: f32,
|
||||||
transparency_enable: f32,
|
transparency_enable: f32,
|
||||||
linetype_scale: f32,
|
linetype_scale: f32,
|
||||||
_pad: f32,
|
fillmode_enable: f32,
|
||||||
view_rot: mat4x4<f32>,
|
view_rot: mat4x4<f32>,
|
||||||
eye_high: vec3<f32>,
|
eye_high: vec3<f32>,
|
||||||
_pad_eh: f32,
|
_pad_eh: f32,
|
||||||
|
|
@ -71,6 +71,7 @@ struct VertexOut {
|
||||||
@location(8) cap: vec2<f32>,
|
@location(8) cap: vec2<f32>,
|
||||||
// (segment pixel length, end half-width at A, end half-width at B).
|
// (segment pixel length, end half-width at A, end half-width at B).
|
||||||
@location(9) @interpolate(flat) cap_ends: vec3<f32>,
|
@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
|
// 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;
|
+ ext * hw * u.world_per_pixel;
|
||||||
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||||
out.cap_ends = vec3<f32>(seg_len, hw_a, hw_b);
|
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.pattern_length = c.pattern_length * lt_scale;
|
||||||
out.pat0 = c.pat0 * lt_scale;
|
out.pat0 = c.pat0 * lt_scale;
|
||||||
out.pat1 = c.pat1 * 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;
|
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> {
|
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||||
// Negative pattern length is the persistent-arena tombstone sentinel.
|
// Negative pattern length is the persistent-arena tombstone sentinel.
|
||||||
// Discard before cap/alpha work so deleted slabs cannot write color/depth.
|
// 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) {
|
if cap_clipped(in.cap, in.cap_ends) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
if band_interior_clipped(in) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
if in.pattern_length > 0.0 {
|
if in.pattern_length > 0.0 {
|
||||||
if in.min_elem >= u.world_per_pixel {
|
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) {
|
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) {
|
if cap_clipped(in.cap, in.cap_ends) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
if band_interior_clipped(in) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
if in.pattern_length > 0.0 {
|
if in.pattern_length > 0.0 {
|
||||||
if in.min_elem >= u.world_per_pixel {
|
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) {
|
if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1, in.align_end, in.align_total) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue