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 <noreply@anthropic.com>
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

+38 -18
View File
@@ -51,26 +51,38 @@ pub struct HatchInstance {
pub color: [f32; 4], // 0
pub color2: [f32; 4], // 16 (gradient end)
pub aabb: [f32; 4], // 32 (local-space xmin,ymin,xmax,ymax)
pub world_origin: [f32; 2], // 48 (anchor added back in VS)
pub angle_offset: f32, // 56
pub scale: f32, // 60
pub grad_cos: f32, // 64
pub grad_sin: f32, // 68
pub grad_min: f32, // 72
pub grad_range: f32, // 76
pub mode: u32, // 80 (0=pattern, 1=solid, 2=gradient)
pub visible: u32, // 84 (CPU sets to 0 to skip)
pub boundary_offset: u32, // 88 (first boundary vert index)
pub boundary_count: u32, // 92
pub family_offset: u32, // 96
pub family_count: u32, // 100
pub world_origin: [f32; 2], // 48 (anchor high half, added back in VS)
pub world_origin_low: [f32; 2], // 56 (anchor low residual — double-single)
pub angle_offset: f32, // 64
pub scale: f32, // 68
pub grad_cos: f32, // 72
pub grad_sin: f32, // 76
pub grad_min: f32, // 80
pub grad_range: f32, // 84
pub mode: u32, // 88 (0=pattern, 1=solid, 2=gradient)
pub visible: u32, // 92 (CPU sets to 0 to skip)
pub boundary_offset: u32, // 96 (first boundary vert index)
pub boundary_count: u32, // 100
pub family_offset: u32, // 104
pub family_count: u32, // 108
/// Signed draw-order depth (-1,1); 0.0 = neutral. Applied as a clip-z
/// bias in the vertex shader so this fill orders against other types.
pub draw_depth: f32, // 104
pub _pad0: u32, // 108 (pad to 112 = 16-byte stride)
pub draw_depth: f32, // 112
pub _pad0: u32, // 116 (pad to 128 = 16-byte stride)
pub _pad1: u32, // 120
pub _pad2: u32, // 124
}
const _: () = assert!(std::mem::size_of::<HatchInstance>() == 112);
const _: () = assert!(std::mem::size_of::<HatchInstance>() == 128);
/// Split a hatch's f64 world-origin anchor into double-single (high, low) f32
/// pairs so the GPU keeps sub-unit precision at UTM-scale coordinates.
#[inline]
fn split_origin_ds(o: [f64; 2]) -> ([f32; 2], [f32; 2]) {
let hx = o[0] as f32;
let hy = o[1] as f32;
([hx, hy], [(o[0] - hx as f64) as f32, (o[1] - hy as f64) as f32])
}
/// Mirrors the per-family struct used by the existing per-hatch shader,
/// but the dash slice lives in a separate concatenated DashBuffer (the
@@ -261,11 +273,13 @@ impl HatchBatchedGpu {
if !min_x.is_finite() {
// Empty / all-NaN — skip but keep the slot so indices
// stay in lockstep with the input list (visibility=0).
let (wo_hi, wo_lo) = split_origin_ds(h.world_origin);
instances.push(HatchInstance {
color: h.color,
color2,
aabb: [0.0, 0.0, 0.0, 0.0],
world_origin: h.world_origin.map(|v| v as f32),
world_origin: wo_hi,
world_origin_low: wo_lo,
angle_offset: h.angle_offset,
scale: h.scale.max(1e-6),
grad_cos,
@@ -280,6 +294,8 @@ impl HatchBatchedGpu {
family_count,
draw_depth: h.draw_depth,
_pad0: 0,
_pad1: 0,
_pad2: 0,
});
continue;
}
@@ -300,11 +316,13 @@ impl HatchBatchedGpu {
};
let pad = (diag * 0.8 + max_spacing * 2.0 * h.scale).max(1.0);
let (wo_hi, wo_lo) = split_origin_ds(h.world_origin);
instances.push(HatchInstance {
color: h.color,
color2,
aabb: [min_x - pad, min_y - pad, max_x + pad, max_y + pad],
world_origin: h.world_origin.map(|v| v as f32),
world_origin: wo_hi,
world_origin_low: wo_lo,
angle_offset: h.angle_offset,
scale: h.scale.max(1e-6),
grad_cos,
@@ -319,6 +337,8 @@ impl HatchBatchedGpu {
family_count,
draw_depth: h.draw_depth,
_pad0: 0,
_pad1: 0,
_pad2: 0,
});
}
+8 -3
View File
@@ -58,13 +58,15 @@ pub struct HatchUniformData {
pub grad_sin: f32, // 52: gradient direction sin
pub grad_min: f32, // 56: gradient proj_min
pub grad_range: f32, // 60: gradient proj_range
pub origin: [f32; 2], // 64: hatch-local origin (boundary AABB centre).
pub origin: [f32; 2], // 64: hatch-local origin (boundary AABB centre),
// // high half of the double-single anchor.
// // The pattern fragment shader subtracts this
// // from xz before the perp/perp_step quotient
// // so f32 doesn't catastrophically cancel when
// // world coords are large and pattern spacing
// // is small (e.g. UTM drawing + sub-mm hatch).
pub _pad: [f32; 2], // 72: 16-byte alignment
pub origin_low: [f32; 2], // 72: anchor low residual (double-single) so the
// // vertex clip position stays precise at UTM scale.
} // total 80 bytes
/// Boundary polygon (binding 1). Matches WGSL `Boundary`.
@@ -288,7 +290,10 @@ impl HatchGpu {
grad_min,
grad_range,
origin: [origin[0] as f32, origin[1] as f32],
_pad: [0.0; 2],
origin_low: [
(origin[0] - origin[0] as f32 as f64) as f32,
(origin[1] - origin[1] as f32 as f64) as f32,
],
};
// ── BoundaryData (in snapped-local space) ────────────────────────