GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
droplet: continuous bottom arc (DropletSpec::bow)
The drop's bottom boundary becomes one circular arc — smooth-INTERSECT
(smax, the smin blend sign-flipped) with a disc whose lowest point
touches the drop's bottom center. The radius derives per drop from a
fixed edge rise (R = hx²/2·rise), so a wide drop flattens toward its
middle on its own while a narrow one curves visibly. The rise rides
p_spec_tint.w — the one free slot left in the push block; 0 keeps the
flat bottom run. Default bow 0.12.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/backend/window_runner.rs | 5 ++++-
src/scene/paint.rs | 8 ++++++++
src/vk/shader2d.wgsl | 19 +++++++++++++++++++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 2e1a160..1bca0dc 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2007,6 +2007,9 @@ pub fn tessellate_display_list(
ar *= f;
}
let band = (spec.band.max(0.05) * rect.height).max(1.0);
+ // Bottom-bow edge rise; the shader derives the arc radius
+ // from it per drop (R = hx²/2·rise).
+ let bow = (spec.bow.clamp(0.0, 0.5) * rect.height).min(hy * 0.9);
plate = Some(crate::vk::PlatePush {
rect: [
(rect.x + rect.width * 0.5) * scale,
@@ -2021,7 +2024,7 @@ pub fn tessellate_display_list(
// DE material's (a drop is wetter than the DE's plates).
material: [plate_mat[0], spec.gleam, spec.shine, spec.rim],
host: [sr * scale, spec.clarity.clamp(0.0, 1.0), spec.dome, ar * scale],
- specular_tint: [1.0, 1.0, 1.0, 0.0],
+ specular_tint: [1.0, 1.0, 1.0, bow * scale],
mode: 10.0,
shape: 2.0,
});
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index ebf3a75..18db1c4 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -98,6 +98,13 @@ pub struct DropletSpec {
pub shine: f32,
/// Fresnel rim crest amplitude (the glass-edge brightening).
pub rim: f32,
+ /// Bottom bow: the drop's bottom boundary becomes ONE continuous circular
+ /// arc — lowest at center, rising by `bow` (fraction of height) at the
+ /// drop's side extents. The arc's radius is derived per drop from that
+ /// fixed edge rise, so a wide drop gets a huge radius and the curvature
+ /// stays subtle at the middle while a narrow drop curves visibly. 0
+ /// disables it (flat bottom run between the corner arcs).
+ pub bow: f32,
}
impl Default for DropletSpec {
@@ -118,6 +125,7 @@ impl Default for DropletSpec {
gleam: 1.4,
shine: 32.0,
rim: 0.5,
+ bow: 0.12,
}
}
}
diff --git a/src/vk/shader2d.wgsl b/src/vk/shader2d.wgsl
index 2214a13..7da9948 100644
--- a/src/vk/shader2d.wgsl
+++ b/src/vk/shader2d.wgsl
@@ -354,6 +354,7 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
// p_radii = [sag, belly radius, belly half-width, blend k] px;
// p_host = [sheet bottom-corner radius px, edge clarity 0-1, dome
// amplitude, attach (top-corner) radius px];
+ // p_spec_tint.w = bottom-bow edge rise px (0 = flat bottom run);
// p_mat.w = fresnel rim crest amplitude (droplets carve nothing, so the
// AO slot is free); p_light.w = shaded band width px.
// The silhouette is the smooth union of a film SHEET attached to the top
@@ -395,6 +396,24 @@ fn plate_shade(frag: vec2f, vcol: vec4f) -> vec4f {
d = mix(gb.z, ga.z, hm) - k * hm * (1.0 - hm);
g = mix(gb.xy, ga.xy, hm);
}
+ // Bottom bow (p_spec_tint.w = edge rise, px): smooth-INTERSECT the
+ // drop with a disc whose lowest point touches the drop's bottom
+ // center — the bottom becomes one continuous circular arc, rising by
+ // the given amount at x = ±hx. The radius follows from that fixed
+ // rise (R = hx²/2·rise), so wide drops flatten toward the middle on
+ // their own. smax = -smin(-a,-b): same polynomial blend, sign flipped.
+ let bow = rrect_clip.p_spec_tint.w;
+ if (bow > 0.25) {
+ let bigr = hx * hx / (2.0 * bow);
+ let cc = vec2f(c.x, c.y + hy - bigr);
+ let pc = frag - cc;
+ let dl = max(length(pc), 1e-3);
+ let dc = dl - bigr;
+ let gc = pc / dl;
+ let hm2 = clamp(0.5 + 0.5 * (d - dc) / k, 0.0, 1.0);
+ d = mix(dc, d, hm2) + k * hm2 * (1.0 - hm2);
+ g = mix(gc, g, hm2);
+ }
g = normalize(g);
let din = -d;