From 2a9416464e14ba7bdabc087d6ca1158754b66821 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 22 Jun 2026 20:34:47 +0300 Subject: [PATCH] feat(render): relative-to-eye for the wire pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wire shader now subtracts the eye (high+low f32) per endpoint and transforms by the rotation-only view-projection instead of multiplying absolute positions by a view-projection that bakes in a large eye translation. The math is identical (proj·R·(pos−eye)) but the large eye no longer enters the f32 matrix, so lines stop jittering on pan/zoom/rotate at large (UTM) coordinates and far from world_offset. Geometry low bits are intentionally dropped: (pos − eye_high) is exact in f32 for same-magnitude operands (Sterbenz) and eye_low corrects the eye, so the only residual is a static sub-f32 position error, not motion jitter — no per-vertex low buffer needed. view_rot is cropped in lock-step with view_proj for tiled panes. Co-Authored-By: Claude Opus 4.8 --- src/scene/view/render.rs | 3 +++ src/shaders/wire.wgsl | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 6647bd6ce..82abcc51e 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -744,6 +744,9 @@ impl Scene { let mut uniforms = Uniforms::new(&inst.camera, full_bounds, self.document.header.lineweight_display); uniforms.view_proj = crop_view_proj(uniforms.view_proj, uo, vo, us, vs); + // The RTE matrix is a view-projection too, so the same clip-space crop + // applies — keep it in lock-step with view_proj for tiled / sub-rect panes. + uniforms.view_rot = crop_view_proj(uniforms.view_rot, uo, vo, us, vs); uniforms.viewport_size = [visible_w, visible_h]; uniforms.flat_shade = if flags.flat_shade { 1.0 } else { 0.0 }; uniforms.transparency_enable = if self.transparency_display { 1.0 } else { 0.0 }; diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index 50273a455..63ac1daa7 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -28,6 +28,17 @@ struct Uniforms { // Transparency-display toggle: 1.0 = honour baked alpha, 0.0 = force // every line opaque. transparency_enable: f32, + _pad: vec2, + // ── Relative-to-eye (double-single) ────────────────────────────────── + // view_rot is the rotation-only view-projection; vertices subtract the eye + // (eye_high + eye_low, two f32 emulating f64) before transforming, so the + // large eye translation never enters the f32 matrix → no large-coordinate + // jitter on pan / zoom / rotate. + view_rot: mat4x4, + eye_high: vec3, + _pad_eh: f32, + eye_low: vec3, + _pad_el: f32, } @group(0) @binding(0) var u: Uniforms; @@ -74,8 +85,15 @@ struct VertexOut { let which_end = which_end_arr[vid]; let side = side_arr[vid]; - let clip_a = u.view_proj * vec4(in.pos_a, 1.0); - let clip_b = u.view_proj * vec4(in.pos_b, 1.0); + // Relative-to-eye: subtract the eye (high+low) from each endpoint, then + // transform by the rotation-only view-projection. (pos − eye_high) is exact + // in f32 when both are the same magnitude (Sterbenz), and eye_low corrects + // the eye's sub-f32 residual — so the position tracks the camera smoothly + // at large coordinates instead of snapping onto the f32 grid each frame. + let rel_a = (in.pos_a - u.eye_high) - u.eye_low; + let rel_b = (in.pos_b - u.eye_high) - u.eye_low; + let clip_a = u.view_rot * vec4(rel_a, 1.0); + let clip_b = u.view_rot * vec4(rel_b, 1.0); // NDC of both endpoints. let ndc_a = clip_a.xy / clip_a.w;