fix(linetype): render dotted and dash-dot patterns (#149)
A dot in an LTYPE pattern is a zero-length element. The pattern resolver encoded it as a tiny `0.01 * scale` world-length dash, which had two bad effects: it was sub-pixel at normal zoom so the dash LOD saw the pattern's smallest feature drop below one pixel and collapsed the whole line to solid, and at larger LTSCALE it left only invisible sub-pixel dots between big gaps. Either way dotted and dash-dot linetypes were never visible; only plain dashed (no zero elements) worked. Keep dots as literal 0.0 so they no longer drag `min_elem` down, and render each as a fixed ~1.5 px mark in the wire shader (pixel-snapped via world_per_pixel, mirroring the hatch shader's dot handling). The shader tells a real dot from trailing array padding by element count — the index of the last non-zero element plus one — so no producer needs a sentinel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ce2365e8f9
commit
fcb97b12da
2 changed files with 39 additions and 14 deletions
|
|
@ -889,17 +889,22 @@ pub(crate) fn resolve_pattern(
|
|||
return solid;
|
||||
}
|
||||
|
||||
// Keep dots (element length exactly 0) as 0.0 so the shader can render
|
||||
// them as a fixed ~1 px mark; trailing array slots stay 0.0 padding and
|
||||
// the shader tells the two apart by position (a 0.0 before the last
|
||||
// non-zero element is a dot, trailing 0.0s are padding). The old code
|
||||
// encoded dots as `0.01 * scale` — a tiny world-length dash that went
|
||||
// sub-pixel at normal zoom and dragged the pattern's `min_elem` below one
|
||||
// pixel, so the dash LOD collapsed dotted / dash-dot lines to solid (or,
|
||||
// at larger LTSCALE, left only invisible sub-pixel dots between big
|
||||
// gaps). (#149)
|
||||
let mut pat = [0.0f32; 8];
|
||||
let mut pat_len = 0.0f32;
|
||||
for (i, el) in lt.elements.iter().take(8).enumerate() {
|
||||
let raw = el.length as f32 * scale;
|
||||
let encoded = if raw == 0.0 {
|
||||
0.01 * scale.max(0.01)
|
||||
} else {
|
||||
raw
|
||||
};
|
||||
pat[i] = encoded;
|
||||
pat_len += encoded.abs();
|
||||
// positive = dash, negative = gap, exactly 0 = dot.
|
||||
let v = el.length as f32 * scale;
|
||||
pat[i] = v;
|
||||
pat_len += v.abs();
|
||||
}
|
||||
if pat_len < 1e-6 {
|
||||
return solid;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
// • distance = cumulative arc-length, linearly interpolated from
|
||||
// (distance_a, distance_b) by `which_end`.
|
||||
// • pattern_length > 0 enables the dash test; 0 = solid (no discard).
|
||||
// • pat0/pat1 encode up to 8 elements: positive=dash, negative=gap.
|
||||
// • pat0/pat1 encode up to 8 elements: positive=dash, negative=gap,
|
||||
// exactly 0=dot (rendered as a fixed ~1 px mark). Trailing 0.0 slots are
|
||||
// padding; the real element count is (index of last non-zero) + 1.
|
||||
|
||||
struct Uniforms {
|
||||
viewport_size: vec2<f32>,
|
||||
|
|
@ -154,17 +156,35 @@ struct VertexOut {
|
|||
return out;
|
||||
}
|
||||
|
||||
// Returns true if arc-length `dist` falls inside a dash segment.
|
||||
// Returns true if arc-length `dist` falls inside a dash or on a dot.
|
||||
fn in_dash(dist: f32, pat_len: f32, p0: vec4<f32>, p1: vec4<f32>) -> bool {
|
||||
let d = ((dist % pat_len) + pat_len) % pat_len;
|
||||
var pos = 0.0f;
|
||||
// A dot is a zero-length element: render it as a fixed ~1.5 px mark
|
||||
// (half-width ~0.75 px in world units) so it stays visible at any zoom
|
||||
// instead of vanishing with its zero world-length. Mirrors the hatch
|
||||
// shader's pixel-snapped dot. (#149)
|
||||
let dot_half = u.world_per_pixel * 0.75;
|
||||
let elems = array<f32, 8>(p0.x, p0.y, p0.z, p0.w, p1.x, p1.y, p1.z, p1.w);
|
||||
// Real element count = (index of last non-zero) + 1. Trailing 0.0 slots
|
||||
// are padding; a 0.0 within this range is a real dot.
|
||||
var count = 0u;
|
||||
for (var i = 0u; i < 8u; i++) {
|
||||
if elems[i] != 0.0 { count = i + 1u; }
|
||||
}
|
||||
for (var i = 0u; i < count; i++) {
|
||||
let elem = elems[i];
|
||||
if elem == 0.0 { break; }
|
||||
let len = abs(elem);
|
||||
if d < pos + len { return elem > 0.0; }
|
||||
pos += len;
|
||||
if elem == 0.0 {
|
||||
// Dot centred at `pos` (zero length); light a small mark, wrapped
|
||||
// around the pattern so a dot at 0 also covers the seam at pat_len.
|
||||
let dd = abs(d - pos);
|
||||
if min(dd, pat_len - dd) <= dot_half { return true; }
|
||||
} else if elem > 0.0 {
|
||||
if d >= pos && d < pos + elem { return true; } // inside a dash
|
||||
pos += elem;
|
||||
} else {
|
||||
pos += -elem; // skip a gap
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue