feat(paint): an inflated material whose shading follows the silhouette - #399
Merged
Merged
Conversation
The three presets from #357 compute their highlight on the node's **box** and then clip it. On a star cut out of a rectangle that gives one band of light across the box, not a relief per branch — the shape stays a flat varnished plane, which is what #385 measured against a reference. `inflated` derives the shading from the silhouette instead. The clipped path is rasterised and blurred, and the **gradient of that blurred mask is the surface normal**, lit by the scene's own `light` from #357. Each branch has its own edge, so each gets its own highlight and its own hollow. `bevel` sets how far in the rounding reaches, `softness` the profile from a hard chamfer to a cushion. The first version banded visibly: with a large `bevel` the mask's gradient is shallow, and sampling adjacent pixels quantises it into concentric steps on 8-bit alpha. The gradient stencil now widens with `bevel`, which is the same reason the artefact existed — measure a shallow slope over a longer baseline. Three tests. The decisive one counts separate bright runs along a row cutting two branches, and fails without the relief with `glossy=0, inflated=0`. The other two assert equality and pass both ways on purpose: nothing paints outside the silhouette, and `intensity: 0` is byte-identical to no material at all. Also here, because it obstructed this work four times: `dot_map_terminates_on_a_zero_dot_spacing` guards against a runaway loop with a 10-second wall-clock budget, and exceeded it under parallel build load while painting perfectly well. The budget is 120 seconds now. The guard answers "does this terminate", and 10 versus 120 seconds makes no difference to that question while making every difference to whether the suite is trustworthy during a chantier. Closes #385
LeadcodeDev
added a commit
that referenced
this pull request
Sep 29, 2026
… lerp (#445) Five items from #434. The inflated material stair-steps at wide bevels because the shading alpha is truncated to u8 with a raw cast. PR #399 already fought this for small bevels by measuring a shallow slope over a longer baseline; past bevel 24 the ramp gets shallow enough that the per-pixel delta falls near 1 LSB and truncation holds identical alpha across several pixels. The fix is a 4x4 Bayer ordered dither on the quantization rather than reworking the shading formula, because the defect is in the display of the ramp and not in its maths -- three passing tests pin that material's calibration. dot_map discarded its AnimatedProperties entirely, so draw_progress keyframes on the node validated and did nothing while arc.draw_in stayed a static field. An arc's reveal is now draw_in * draw_progress rather than draw_progress overriding it: an override would make draw_in silently dead the instant anything animated the node, which is a worse trap than the bug. layout-surface translated each cell onto the curve and applied the whole-surface tilt, but never rotated a cell to face the tangent at its own position, so a projected grid stayed a set of flat cards. Each cell now rotates about its own centre by the same angles that placed it, which leaves the centre cell untouched as documented. The issue's own repro expected an edge column near 120px; it measured 144 and now measures 120. interpolate_animated_bg and lerp_zones had no production call site at all -- reachable only from their own tests -- while SKILL.md and halo-shapes.md both describe background properties interpolating across a transition. render_frame_v2_scaled cross-faded two fully rendered frame buffers instead, so two static copies ghosted past each other. It now pairs the layers and draws one interpolated result, falling back to the old cross-fade only for surplus layers when the two scenes declare different counts. An outset box-shadow drew its rrect with nothing excluding the box's own footprint. An opaque background hid it; a transparent one let the shadow's solid core through, so probing a box's centre returned the shadow colour. Clipping with Difference against the border box is what CSS specifies and what the inset branch already did for its own hole. Closes #434
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #385. Refs #388.
The gap it closes
The three presets from #357 compute their highlight on the node's box, then clip it. On a five-branch star cut out of a rectangle, that is one band of light across the box — the shape reads as a flat varnished plane, not a volume.
inflatedderives the shading from the silhouette: the clipped path is rasterised and blurred, and the gradient of that blurred mask is the surface normal, lit by the scene's ownlight. Each branch has its own edge, so each gets its own highlight and hollow.Rendered side by side —
glossy,inflatedatbevel: 26, softness: 0.6,inflatedatbevel: 8, softness: 0.1— the first is one flat gradient across the bounding box and the other two have relief on every branch.An artefact I had to fix, not ship
The first version banded visibly: with a large
bevelthe mask's gradient is shallow, and sampling adjacent pixels quantises it into concentric steps on 8-bit alpha. The gradient stencil now widens withbevel— the same fact that caused the artefact is what fixes it: measure a shallow slope over a longer baseline.Verification
Three tests. The decisive one counts separate bright runs along a scanline cutting two branches:
The other two assert equality and pass both ways on purpose — nothing paints outside the silhouette, and
intensity: 0is byte-identical to declaring no material.Three things the rule file leads with
bevelmust stay small relative to the shape. It is both the blur width and the scale the slope is measured at, so a large bevel on a small shape drowns the silhouette before a normal can be taken from it. At most a quarter of the shape's smallest dimension.highlightandedge. No specular return at all — its light is entirely geometric. Next to aglossynode that is two lighting models in one frame, which may be intended but is not a tuning variant.Also in here
dot_map_terminates_on_a_zero_dot_spacingguards against a runaway loop with a 10-second wall-clock budget, and blew it four separate times today under parallel build load while painting perfectly well. It is 120 seconds now. The guard answers "does this terminate", and 10 versus 120 seconds makes no difference to that question while making every difference to whether the suite can be trusted during a chantier.Two
ffmpeg-dependent tests have the same shape of problem from a different cause; they are not touched here.cargo fmt --all --checkandcargo clippy --workspace --all-targets -- -D warningsclean.Written comment-free, per the codebase-wide rule from #345.