git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit675de1b832b7b50daf75dd94b133b66f922709cc
parent50fcb02360
authorLucas Galante <[email protected]>
date2026-09-14 09:12
fix(relief): mitre the lattice wall's outer edge instead of offsetting it

The offset curve rounds every corner at radius + run, so with a run of
half a rail each crossing swept in big arcs while the cells kept tight
corners. The outer edge is now the cell grown by the run at the SAME
corner radius, and the wall is the fraction across the band between the
two contours — distance / run on the straights, four walls meeting on
the diagonals at a crossing. Height field mirrored; the test pins the
outer contour at the cell's radius.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/scene/heightfield.rs | 26 +++++++++++++++++++++-----
 src/scene/paint.rs       |  4 ++++
 src/vk/shader2d.wgsl     | 22 ++++++++++++++++++----
 3 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/src/scene/heightfield.rs b/src/scene/heightfield.rs
index 64352ce..4242ae1 100644
--- a/src/scene/heightfield.rs
+++ b/src/scene/heightfield.rs
@@ -348,12 +348,17 @@ impl HeightField {
                             // well (see the shader's MODE_LATTICE). Positive
                             // outside the cell; the wall runs from the edge
                             // outward over t, floor at the edge.
+                            // Mitred, not offset: the wall's outer edge is the
+                            // cell grown by t at the SAME corner radius, and u
+                            // is the fraction across the band between the two
+                            // contours (1 at the cell edge, 0 at the outer).
                             let (px, py) = (p.host[0].max(1e-3), p.host[1].max(1e-3));
                             let c = (pt.0 - p.rect[0], pt.1 - p.rect[1]);
                             let c = (c.0 - px * (c.0 / px).round(), c.1 - py * (c.1 / py).round());
-                            let d_out = rr_sdf(c, [0.0, 0.0, p.rect[2], p.rect[3]], p.radii, shape, t);
-                            let u = (1.0 - d_out / t).clamp(0.0, 1.0);
-                            -carve_drop * prof.carve_height(u)
+                            let d_in = rr_sdf(c, [0.0, 0.0, p.rect[2], p.rect[3]], p.radii, shape, t);
+                            let d_out = rr_sdf(c, [0.0, 0.0, p.rect[2] + t, p.rect[3] + t], p.radii, shape, t);
+                            let frac = (d_in / (d_in - d_out).max(1e-3)).clamp(0.0, 1.0);
+                            -carve_drop * prof.carve_height(1.0 - frac)
                         }
                         MODE_UNION => {
                             // Nearest box of the run — the union SDF — through
@@ -571,9 +576,20 @@ mod tests {
         assert!((at(49, 25) - at(50, 25)).abs() < 1e-3, "rail centre symmetric {} {}", at(49, 25), at(50, 25));
         assert!(at(50, 25) > -0.1 && at(50, 25) <= 0.0, "rail centre {}", at(50, 25));
         assert!(at(50, 25) > at(45, 25), "rail centre above the wall");
-        // The crossing where four cells meet is farther from every cell than
-        // the wall runs: plateau — the mitre the per-cell rings never gave.
+        // The crossing where four cells meet is outside every cell's outer
+        // contour: plateau — the mitre the per-cell rings never gave.
         assert!(at(50, 50).abs() < 1e-3, "crossing {}", at(50, 50));
+        // The outer contour keeps the CELL's corner radius (4), not radius +
+        // run (14): the wall reaches to within a corner's rounding of the
+        // crossing centre. (48.5, 48.5) sits 0.46 px inside the outer box's
+        // corner arc — a hair of carve — where an offset-curve outer contour
+        // (13.7 px from the cell, past the 10 px run) would be flat.
+        assert!(at(48, 48) < 0.0 && at(48, 48) > -0.15, "near-crossing {}", at(48, 48));
+        // On the rail centre line the outer contour is straight: plateau all
+        // the way up to the crossing's corner rounding (the same half-px
+        // straddle as above: 49.5 and 50.5 sit a hair into opposite walls).
+        assert!((at(49, 45) - at(50, 45)).abs() < 1e-3, "rail centre near crossing symmetric");
+        assert!(at(50, 45) > -0.1 && at(50, 45) <= 0.0, "rail centre near crossing {}", at(50, 45));
         // Halfway out the wall is between floor and plateau, on both sides
         // of the rail (one wall from each cell, symmetric). Pixel centres:
         // 45.5 is 5.5 past the first cell's edge at 40, 54.5 is 5.5 before
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index ab10558..58ea8d9 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -587,6 +587,10 @@ pub enum Prim {
     /// edge OUTWARD over `depth` px (floor at the edge, plateau one run out),
     /// so a rail between two cells carries one wall from each side and the
     /// rail face is whatever the runs leave. Shading lands only inside `rect`.
+    /// The wall's outer edge is MITRED, not offset: it is the cell grown by
+    /// the run at the same `radius`, so a crossing keeps the cell's corner
+    /// rounding instead of sweeping at `radius + depth`, and the four walls
+    /// meet on the diagonals.
     ///
     /// This exists because a lattice drawn as one [`Prim::Recess`] per cell is
     /// N independent overlays: where four rounded rings meet at a crossing
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 4ccc3f6..35c4225 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -685,14 +685,28 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
         // union distance of every well. One profile evaluation, so the
         // rails between cells and the diagonals at each crossing are true
         // mitres instead of stacked per-cell overlays. The wall runs from
-        // the cell edge OUTWARD: floor at the edge, plateau one run out —
-        // the +t/2 shifts the shared path's boundary-straddling band so it
-        // spans [edge, edge + t]. Rejoins the free-carve path as a recess.
+        // the cell edge OUTWARD: floor at the edge, plateau one run out.
+        //
+        // The wall's outer edge is NOT the offset curve (every point one run
+        // from the cell): that contour rounds each corner at radius + run,
+        // and with a run of half a rail the crossings read as big sweeping
+        // arcs while the cells themselves keep tight corners. A moulding
+        // does not offset its corners, it mitres them — so the outer edge is
+        // the cell box grown by the run with the SAME corner radius, and the
+        // wall is the fraction of the way across the band between the two
+        // contours (u = 1 at the cell edge, 0 at the outer contour). On the
+        // straight rails that is exactly distance / run; around a corner the
+        // band widens along the diagonal and four walls meet on the mitre
+        // lines. Lit by the cell's gradient. The -t/2 recentres the shared
+        // path's boundary-straddling band on [edge, edge + t].
         let per = max(rrect_clip.p_host.xy, vec2f(1e-3));
         var c = frag - rrect_clip.p_rect.xy;
         c = c - per * round(c / per);
         let lg = rr_sdf_grad(c, vec4f(0.0, 0.0, rrect_clip.p_rect.zw), rrect_clip.p_radii);
-        fd = -lg.z + 0.5 * t;
+        let lo = rr_sdf_grad(c, vec4f(0.0, 0.0, rrect_clip.p_rect.zw + vec2f(t)), rrect_clip.p_radii);
+        let band = max(lg.z - lo.z, 1e-3);
+        let frac = clamp(lg.z / band, 0.0, 1.0);
+        fd = (0.5 - frac) * t;
         fgd = lg.xy;
         eff = MODE_RECESS;
     } else if (mode == MODE_UNION) {