Screen-space join geometry at degree-2 polyline joints (#790), shared by the
visual and picking GLSL vertex stages.
Two segments meeting at a turn of angle theta leave an uncovered circular
sector of that angle on the OUTSIDE of the bend and double-cover a lens on
the inside: dark ticks along the convex edge of any thick curve, bright ticks
along the concave one. No per-endpoint intensity scalar can close the outer
wedge, because nothing rasterises there to shade — only geometry can.
luxarLineJoin rotates this quad's end edge onto the shared miter edge so
the two quads TILE. Coverage becomes a partition, so there is nothing to sum
and every blending mode is correct by construction, with no axial profile and
no cap dimming.
That partition is exact in exact arithmetic, not bit-exact in float32. The
two sides of a joint evaluate algebraically identical operands in a
canonical order, but they reach pixel space differently — the vertex stage
scales the NDC difference ndcEnd - ndcStart once, while
luxarLinePixelPos scales each endpoint and the caller subtracts afterwards
— so their miter points agree only to float32 rounding, order 1e-5 px on the
test_line_joins fixture. That is at most a single seam pixel under the
rasteriser's subpixel quantisation, and it is why the acceptance spec
(tests/e2e/line-join-artifact.spec.ts, which quantifies it) gates the bend
bands at a couple of outlier pixels rather than at the zero they measure.
Subtracting in NDC and scaling afterwards on both paths — here and in the
TSL twin — would make the two sides bit-exact; that is a known, deliberately
deferred change.
The two stages MUST build the same quad or a pick footprint stops matching
what the eye sees, so this lives here rather than being written twice.
REQUIRED GLOBALS (same implicit-context pattern as GLSL_SORTED_INDEX):
uLineTex, uResolution, uIsOrtho, luxarProjectionSizeScale
(GLSL_PROJECTION_FUNCTIONS), modelViewMatrix, projectionMatrix, and
luxarSortedIndex(). Include this block AFTER those declarations — GLSL
resolves names top-down. uLineJoin is declared here, so an including
shader must not declare it again.
Screen-space join geometry at degree-2 polyline joints (#790), shared by the visual and picking GLSL vertex stages.
Two segments meeting at a turn of angle theta leave an uncovered circular sector of that angle on the OUTSIDE of the bend and double-cover a lens on the inside: dark ticks along the convex edge of any thick curve, bright ticks along the concave one. No per-endpoint intensity scalar can close the outer wedge, because nothing rasterises there to shade — only geometry can.
luxarLineJoinrotates this quad's end edge onto the shared miter edge so the two quads TILE. Coverage becomes a partition, so there is nothing to sum and every blending mode is correct by construction, with no axial profile and no cap dimming.That partition is exact in exact arithmetic, not bit-exact in float32. The two sides of a joint evaluate algebraically identical operands in a canonical order, but they reach pixel space differently — the vertex stage scales the NDC difference
ndcEnd - ndcStartonce, whileluxarLinePixelPosscales each endpoint and the caller subtracts afterwards — so their miter points agree only to float32 rounding, order 1e-5 px on thetest_line_joinsfixture. That is at most a single seam pixel under the rasteriser's subpixel quantisation, and it is why the acceptance spec (tests/e2e/line-join-artifact.spec.ts, which quantifies it) gates the bend bands at a couple of outlier pixels rather than at the zero they measure. Subtracting in NDC and scaling afterwards on both paths — here and in the TSL twin — would make the two sides bit-exact; that is a known, deliberately deferred change.The two stages MUST build the same quad or a pick footprint stops matching what the eye sees, so this lives here rather than being written twice.
REQUIRED GLOBALS (same implicit-context pattern as
GLSL_SORTED_INDEX):uLineTex,uResolution,uIsOrtho,luxarProjectionSizeScale(GLSL_PROJECTION_FUNCTIONS),modelViewMatrix,projectionMatrix, andluxarSortedIndex(). Include this block AFTER those declarations — GLSL resolves names top-down.uLineJoinis declared here, so an including shader must not declare it again.