fix(solid3d): double-single split for shaded ACIS faces at UTM

A 3D solid's shaded faces used absolute f32 world coordinates while its
feature edges carried the double-single (high, low) pair the mesh shader
reconstructs relative to the eye. At UTM scale the faces snapped to a
~0.06 m grid, so the shaded surface crawled against its own wireframe as
the camera moved.

`apply_body_transform` is where a solid gets its world placement — the
large body translation is exactly where the coordinate stops fitting an
f32. It now computes each world vertex in f64 and splits it into the pair,
filling `verts_low` the same way the feature edges already are. Both
tessellation paths (truck B-rep and the bespoke sampler) route through it,
and the downstream re-split points (top-level offset, per-INSTANCE block
transform) already consume the pair, so they inherit the corrected
residual. Picking, which reads `verts_low` too, sharpens at UTM as well.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-17 19:15:02 +03:00
1 parent 5e1be74651
commit 26e83a90bf
1 file changed
+18 -5
+18 -5
View File
@@ -297,12 +297,27 @@ pub(crate) fn body_transform(sat: &SatDocument) -> Option<([f64; 9], [f64; 3], f
/// vectors (`p' = scale·(p·M) + T`), so the 3×3 is indexed transposed relative
/// to a column-vector multiply. Normals get the rotation only, renormalized.
pub(crate) fn apply_body_transform(mesh: &mut MeshModel, m: &[f64; 9], tr: &[f64; 3], scale: f64) {
for v in &mut mesh.verts {
let (x, y, z) = (v[0] as f64, v[1] as f64, v[2] as f64);
// The body translation `tr` is where a solid gets its world placement, so at
// UTM scale this is exactly where the coordinate stops fitting in an f32.
// Compute each world vertex in f64 and split it into the double-single
// (high, low) pair the mesh shader reconstructs relative to the eye — the
// same treatment the feature edges already get. Without the low half the
// shaded faces sit on a ~0.06 m grid while their own edges stay exact, so the
// surface visibly crawls against its wireframe as the camera moves.
mesh.verts_low = Vec::with_capacity(mesh.verts.len());
for i in 0..mesh.verts.len() {
let [vx, vy, vz] = mesh.verts[i];
let (x, y, z) = (vx as f64, vy as f64, vz as f64);
let wx = scale * (x * m[0] + y * m[3] + z * m[6]) + tr[0];
let wy = scale * (x * m[1] + y * m[4] + z * m[7]) + tr[1];
let wz = scale * (x * m[2] + y * m[5] + z * m[8]) + tr[2];
*v = [wx as f32, wy as f32, wz as f32];
let (hx, hy, hz) = (wx as f32, wy as f32, wz as f32);
mesh.verts[i] = [hx, hy, hz];
mesh.verts_low.push([
(wx - hx as f64) as f32,
(wy - hy as f64) as f32,
(wz - hz as f64) as f32,
]);
}
for n in &mut mesh.normals {
let (x, y, z) = (n[0] as f64, n[1] as f64, n[2] as f64);
@@ -1142,5 +1157,3 @@ fn norm3(v: [f64; 3]) -> [f64; 3] {
[v[0] / len, v[1] / len, v[2] / len]
}
}