feat(render): double-single anchors for both hatch pipelines

The hatch anchor (world_origin / pattern origin) was cast to a single
f32, quantizing UTM-scale fills to ~0.5 m and reintroducing jitter the
wire path no longer had. Both the batched and per-hatch pipelines now
carry the anchor as a high/low f32 pair (HatchInstance.world_origin_low,
HatchUniformData.origin_low) and reconstruct the eye-relative position in
the shader as (origin_high - eye_high) + (local + origin_low - eye_low).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-06-23 18:28:52 +03:00
1 parent 6a74f12a92
commit 0f7920dc2c
4 files changed
+71 -29

No files matched your search

+10 -3
View File
@@ -53,7 +53,7 @@ struct HatchUniforms {
// // `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
origin_low: vec2<f32>, // 72: anchor low residual (double-single)
}
@group(1) @binding(0) var<uniform> h: HatchUniforms;
@@ -104,8 +104,15 @@ struct VOut {
// 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_rot * vec4<f32>((world - u.eye_high) - u.eye_low, 1.0);
// Double-single relative-to-eye: anchor high cancels eye_high (Sterbenz);
// boundary-local v.pos + anchor low + (−eye_low) carry the residual.
let hi = vec3<f32>(h.origin.x - u.eye_high.x,
h.origin.y - u.eye_high.y,
-u.eye_high.z);
let lo = vec3<f32>(v.pos.x + h.origin_low.x - u.eye_low.x,
v.pos.y + h.origin_low.y - u.eye_low.y,
v.pos.z - u.eye_low.z);
o.clip = u.view_rot * vec4<f32>(hi + lo, 1.0);
o.xz = vec2<f32>(v.pos.x, v.pos.y);
return o;
}
+15 -5
View File
@@ -41,7 +41,8 @@ struct HatchInstance {
color: vec4<f32>,
color2: vec4<f32>,
aabb: vec4<f32>, // (xmin, ymin, xmax, ymax) — local space
world_origin: vec2<f32>,
world_origin: vec2<f32>, // anchor high half
world_origin_low: vec2<f32>, // anchor low residual (double-single)
angle_offset: f32,
scale: f32,
grad_cos: f32,
@@ -56,6 +57,8 @@ struct HatchInstance {
family_count: u32,
draw_depth: f32, // signed (-1,1) draw-order bias; 0 = neutral
_pad0: u32,
_pad1: u32,
_pad2: u32,
}
// Draw-order depth bias (see wire.wgsl). Higher draw_depth → smaller z →
@@ -132,10 +135,17 @@ fn corner_xy(c: u32, aabb: vec4<f32>) -> vec2<f32> {
}
let local = corner_xy(v.corner, inst.aabb);
let world = vec3<f32>(local.x + inst.world_origin.x,
local.y + inst.world_origin.y,
0.0);
o.clip = u.view_rot * vec4<f32>((world - u.eye_high) - u.eye_low, 1.0);
// Double-single relative-to-eye: the anchor high half cancels exactly
// against eye_high (Sterbenz); local + anchor low + (−eye_low) carry the
// residual. `local` is small (boundary-relative), so adding it in the low
// term keeps full precision at UTM-scale anchors.
let hi = vec3<f32>(inst.world_origin.x - u.eye_high.x,
inst.world_origin.y - u.eye_high.y,
-u.eye_high.z);
let lo = vec3<f32>(local.x + inst.world_origin_low.x - u.eye_low.x,
local.y + inst.world_origin_low.y - u.eye_low.y,
-u.eye_low.z);
o.clip = u.view_rot * vec4<f32>(hi + lo, 1.0);
o.clip.z = o.clip.z - inst.draw_depth * DRAW_ORDER_BIAS * o.clip.w;
o.xz = local;
o.instance_index = v.instance_index;