feat(render): relative-to-eye for the wire pipeline

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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-23 18:28:52 +03:00
1 parent 8f16ba1ac2
commit 2a9416464e
2 files changed
+23 -2

No files matched your search

+3
View File
@@ -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 };
+20 -2
View File
@@ -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<f32>,
// ── 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<f32>,
eye_high: vec3<f32>,
_pad_eh: f32,
eye_low: vec3<f32>,
_pad_el: f32,
}
@group(0) @binding(0) var<uniform> 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<f32>(in.pos_a, 1.0);
let clip_b = u.view_proj * vec4<f32>(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<f32>(rel_a, 1.0);
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
// NDC of both endpoints.
let ndc_a = clip_a.xy / clip_a.w;