fix(scene): hatch boundary + pattern precision for large-WCS drawings
Two precision problems were producing visibly wrong hatches on UTM-scale (and similarly-offset) drawings even after world_offset was correct: 1. **Boundary tessellation** — `hatch_model_from_dxf`'s polyline-bulge arc tessellation ran the whole arc-center computation in f32. With vertices at WCS magnitude ~1e5 the f32 ULP is ~1 cm, which propagated into the sampled arc points and showed up as visibly wavy / jagged hatch boundaries on edges that crossed the long axis of the drawing. Re-run the full bulge → arc math in f64; only cast at the to_xy call that feeds the (also f64) boundary buffer. 2. **Pattern phase** — the per-fragment `perp / perp_step` modulo blew precision when `xz` was at offset-rel WCS magnitude and perp_step was sub-cm. Move the precision-critical bits out of f32 by keeping each hatch's anchor in f64 on the model side: `HatchModel` now carries a `world_origin: [f64; 2]` and the `boundary: [f32; 2]` vertices are stored as small offsets from it. The GPU vertex shader adds `origin` back inside the view_proj multiply for clip position (sub-pixel-lossy at large hatches but invisible); the `xz` varying that the fragment shader uses for in_polygon + pattern math stays in hatch-local space at full f32 precision. For pattern alignment across adjacent hatches with the same family, the GPU pipeline snaps the anchor to the family's perp/along grid in f64 before sending it down. Snap uses the same QCAD-PAT convention (`fam.dy = perp_step`, `fam.dx = along_step`) the shader's check_family uses — an earlier attempt used the world-frame `(-dx*sin + dy*cos)` formula, which produced a meaningless grid and no alignment. Trade-off: pattern phase is "global modulo per-family-grid" rather than tied to WCS (0,0). Same hatches still align with each other; the absolute phase relative to the WCS origin may differ from the pre-fix behaviour on small drawings. Consumers that read `model.boundary` directly (hit_test, paper_canvas, add_hatch round-trip) reconstruct WCS via `boundary[i] + world_origin`. The hatch wgsl bind-group entry for binding 0 was bumped to `VERTEX | FRAGMENT` because the vertex shader now reads `h.origin`. The fragment-shader hatch LOD substitute + sub-pixel skip frome7675beand the scissor plumbing fromfbbac42still work — those touched different fields of the same struct. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
dc80968023
commit
172c538159
8 changed files with 238 additions and 63 deletions
|
|
@ -39,6 +39,12 @@ struct HatchUniforms {
|
|||
grad_sin: f32, // 52: gradient direction sin
|
||||
grad_min: f32, // 56: gradient proj_min
|
||||
grad_range: f32, // 60: gradient proj_range
|
||||
origin: vec2<f32>, // 64: hatch-local pattern origin (boundary
|
||||
// // centre). Pattern math runs against
|
||||
// // `xz - origin` so the f32 modulo doesn't
|
||||
// // catastrophically cancel when world coords
|
||||
// // are large and pattern spacing is small.
|
||||
_pad: vec2<f32>, // 72: 16-byte alignment
|
||||
}
|
||||
@group(1) @binding(0) var<uniform> h: HatchUniforms;
|
||||
|
||||
|
|
@ -81,7 +87,16 @@ struct VOut {
|
|||
|
||||
@vertex fn vs_main(v: VIn) -> VOut {
|
||||
var o: VOut;
|
||||
o.clip = u.view_proj * vec4<f32>(v.pos, 1.0);
|
||||
// v.pos comes in pre-shifted to hatch-local space (CPU subtracted
|
||||
// h.origin from the quad). Add origin back inside the view_proj
|
||||
// multiply to land at the correct world-space clip position. The
|
||||
// f32+f32 addition can lose sub-mm precision at very far hatches
|
||||
// but the result feeds view_proj for screen NDC where sub-pixel
|
||||
// jitter is invisible. The local-space `xz` we export to the
|
||||
// fragment shader stays at full f32 precision so the in_polygon /
|
||||
// pattern math doesn't catastrophically cancel.
|
||||
let world = vec3<f32>(v.pos.x + h.origin.x, v.pos.y + h.origin.y, v.pos.z);
|
||||
o.clip = u.view_proj * vec4<f32>(world, 1.0);
|
||||
o.xz = vec2<f32>(v.pos.x, v.pos.y);
|
||||
return o;
|
||||
}
|
||||
|
|
@ -213,7 +228,12 @@ fn check_family(
|
|||
}
|
||||
}
|
||||
|
||||
// 4. Pattern: evaluate each line family.
|
||||
// 4. Pattern: v.xz is already in hatch-local space (CPU pre-shifted
|
||||
// quad + BoundaryData by h.origin). The PAT family origin
|
||||
// (typically (0,0)) is treated as relative to h.origin, so
|
||||
// pattern phase is per-hatch rather than world-aligned — that
|
||||
// trade-off is the price of f32 precision at large drawing
|
||||
// extents.
|
||||
let cos_off = cos(h.angle_offset);
|
||||
let sin_off = sin(h.angle_offset);
|
||||
for (var i = 0u; i < f.n_families; i++) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue