Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 61 additions & 98 deletions src/shaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,27 +73,6 @@ export const defaultShaderRadialGradient: ShaderRadialGradient =
export const defaultShaderLinearGradient: ShaderLinearGradient =
lngr_shaders.LinearGradient;

function calcFactoredRadiusArray(
radius: Vec4,
width: number,
height: number,
out: Vec4 = [0, 0, 0, 0],
): Vec4 {
[out[0], out[1], out[2], out[3]] = radius;
let factor = Math.min(
width / Math.max(width, radius[0] + radius[1]),
width / Math.max(width, radius[2] + radius[3]),
height / Math.max(height, radius[0] + radius[3]),
height / Math.max(height, radius[1] + radius[2]),
1,
);
out[0] *= factor;
out[1] *= factor;
out[2] *= factor;
out[3] *= factor;
return out;
}

function toValidVec4(value: unknown): Vec4 {
if (typeof value === 'number') {
return [value, value, value, value];
Expand Down Expand Up @@ -209,66 +188,22 @@ const roundedWithBorderProps: lngr.ShaderProps<ShaderRoundedWithBorderProps> = {
export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
props: roundedWithBorderProps,
canBatch: () => false,
update(node) {
update() {
let props = this.props!;
let borderWidth = props['border-width'] as Vec4;
let borderGap = props['border-gap'];
let inset = props['border-inset'];

let [b_t, b_r, b_b, b_l] = borderWidth;

this.uniformRGBA('u_borderColor', props['border-color']);
this.uniform4fa('u_border', borderWidth);
this.uniform1f('u_gap', borderGap);
this.uniform1i('u_inset', inset ? 1 : 0);

// Check if border is zero (no border widths)
let [b_t, b_r, b_b, b_l] = borderWidth;
let borderZero = b_t === 0 && b_r === 0 && b_b === 0 && b_l === 0;
this.uniform1i('u_borderZero', borderZero ? 1 : 0);

let origWidth = node.width;
let origHeight = node.height;
this.uniform2f('u_dimensions_orig', origWidth, origHeight);

let finalWidth = origWidth;
let finalHeight = origHeight;
if (!inset) {
// For outside borders, expand dimensions
finalWidth = origWidth + b_l + b_r + borderGap * 2;
finalHeight = origHeight + b_t + b_b + borderGap * 2;
}

// u_dimensions for the shader's SDF functions
this.uniform2f('u_dimensions', finalWidth, finalHeight);

// The `radius` property is for the content rectangle.
// Factor it against the appropriate dimensions to prevent self-intersection.
let contentRadius = calcFactoredRadiusArray(
props.radius as Vec4,
origWidth,
origHeight,
);

// Calculate the appropriate radius for the shader based on inset mode
let finalRadius = contentRadius;
if (!inset) {
// For each corner, the total radius is content radius + gap + border thickness.
// Border thickness at a corner is approximated as the max of the two adjacent border sides.
let outerRadius: Vec4 = [
contentRadius[0] + borderGap + Math.max(b_t, b_l), // top-left
contentRadius[1] + borderGap + Math.max(b_t, b_r), // top-right
contentRadius[2] + borderGap + Math.max(b_b, b_r), // bottom-right
contentRadius[3] + borderGap + Math.max(b_b, b_l), // bottom-left
];
calcFactoredRadiusArray(
outerRadius,
finalWidth,
finalHeight,
finalRadius,
);
}

this.uniform4fa('u_radius', finalRadius);
this.uniform4fa('u_radius_node', props.radius as Vec4);
},
vertex: /*glsl*/ `
# ifdef GL_FRAGMENT_PRECISION_HIGH
Expand All @@ -285,11 +220,10 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {

uniform vec2 u_resolution;
uniform float u_pixelRatio;

/* Passed by shader setup */
uniform vec2 u_dimensions;
uniform vec2 u_dimensions_orig;
uniform vec4 u_radius;

/* Passed by shader setup */
uniform vec4 u_radius_node;
uniform vec4 u_border;
uniform float u_gap;
uniform bool u_inset;
Expand All @@ -304,6 +238,17 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
varying vec4 v_innerRadius;
varying vec2 v_innerSize;
varying vec2 v_halfDimensions;
varying vec4 v_radius_border;

// Calculate factored radius to prevent self-intersection
vec4 calcFactoredRadius(vec4 radius, vec2 dimensions) {
float factor = 1.0;
factor = min(factor, dimensions.x / max(dimensions.x, radius.x + radius.y));
factor = min(factor, dimensions.x / max(dimensions.x, radius.z + radius.w));
factor = min(factor, dimensions.y / max(dimensions.y, radius.x + radius.w));
factor = min(factor, dimensions.y / max(dimensions.y, radius.y + radius.z));
return radius * factor;
}

void main() {
vec2 screen_space = vec2(2.0 / u_resolution.x, -2.0 / u_resolution.y);
Expand All @@ -316,6 +261,26 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
float b_b = u_border.z;
float b_l = u_border.w;

vec2 rect_node = u_dimensions;
vec2 rect_border = u_dimensions;
if (!u_inset) {
// For outside borders, expand dimensions
rect_border.x += b_l + b_r + u_gap * 2.0;
rect_border.y += b_t + b_b + u_gap * 2.0;
}

// factored content radius
v_radius_border = calcFactoredRadius(u_radius_node, rect_node);

// For outside borders, add gap and border thickness to radius
if (!u_inset) {
v_radius_border.x += u_gap + max(b_t, b_l); // top-left
v_radius_border.y += u_gap + max(b_t, b_r); // top-right
v_radius_border.z += u_gap + max(b_b, b_r); // bottom-right
v_radius_border.w += u_gap + max(b_b, b_l); // bottom-left
v_radius_border = calcFactoredRadius(v_radius_border, rect_border);
}

// Calculate the offset to expand/contract the quad for border and gap
vec2 expansion_offset = vec2(0.0);
if (!u_inset) {
Expand All @@ -331,18 +296,17 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
expansion_offset.y = (b_b + u_gap);
}
}
// For inset borders, no expansion needed - use original position

// Texture coordinate calculation
v_texcoords = a_textureCoords;
if (!u_inset) { // For outside borders, adjust texture coordinates for expansion
v_texcoords *= u_dimensions;
v_texcoords *= rect_border;
v_texcoords.x -= b_l + u_gap;
v_texcoords.y -= b_t + u_gap;
v_texcoords /= u_dimensions_orig;
v_texcoords /= rect_node;
}

v_halfDimensions = u_dimensions * 0.5;
v_halfDimensions = rect_border * 0.5;
if (!u_borderZero) {

float gap_x2 = u_gap * 2.0;
Expand All @@ -353,32 +317,32 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
// v_innerRadius/Size represents the border area

// Gap area (v_borderEnd represents gap boundary) - uniform gap
v_borderEndRadius = u_radius - u_gap - 0.5;
v_borderEndSize = (u_dimensions - gap_x2 - 1.0) * 0.5;
v_borderEndRadius = v_radius_border - u_gap - 0.5;
v_borderEndSize = (rect_border - gap_x2 - 1.0) * 0.5;

// Border area (v_inner represents border boundary) - individual border widths
v_innerRadius.x = u_radius.x - u_gap - max(b_t, b_l) - 0.5;
v_innerRadius.y = u_radius.y - u_gap - max(b_t, b_r) - 0.5;
v_innerRadius.z = u_radius.z - u_gap - max(b_b, b_r) - 0.5;
v_innerRadius.w = u_radius.w - u_gap - max(b_b, b_l) - 0.5;
v_innerRadius.x = v_radius_border.x - u_gap - max(b_t, b_l) - 0.5;
v_innerRadius.y = v_radius_border.y - u_gap - max(b_t, b_r) - 0.5;
v_innerRadius.z = v_radius_border.z - u_gap - max(b_b, b_r) - 0.5;
v_innerRadius.w = v_radius_border.w - u_gap - max(b_b, b_l) - 0.5;

v_innerSize = (u_dimensions - gap_x2 - vec2(b_l + b_r, b_t + b_b) - 1.0) * 0.5;
v_innerSize = (rect_border - gap_x2 - vec2(b_l + b_r, b_t + b_b) - 1.0) * 0.5;
} else {
// For outside borders, calculate from expanded dimensions inward
v_borderEndRadius.x = u_radius.x - max(b_t, b_l) - 0.5;
v_borderEndRadius.y = u_radius.y - max(b_t, b_r) - 0.5;
v_borderEndRadius.z = u_radius.z - max(b_b, b_r) - 0.5;
v_borderEndRadius.w = u_radius.w - max(b_b, b_l) - 0.5;
v_borderEndRadius.x = v_radius_border.x - max(b_t, b_l) - 0.5;
v_borderEndRadius.y = v_radius_border.y - max(b_t, b_r) - 0.5;
v_borderEndRadius.z = v_radius_border.z - max(b_b, b_r) - 0.5;
v_borderEndRadius.w = v_radius_border.w - max(b_b, b_l) - 0.5;

v_borderEndSize = (u_dimensions - vec2(b_l + b_r, b_t + b_b) - 1.0) * 0.5;
v_borderEndSize = (rect_border - vec2(b_l + b_r, b_t + b_b) - 1.0) * 0.5;

v_innerRadius.x = u_radius.x - max(b_t, b_l) - u_gap - 0.5;
v_innerRadius.y = u_radius.y - max(b_t, b_r) - u_gap - 0.5;
v_innerRadius.z = u_radius.z - max(b_b, b_r) - u_gap - 0.5;
v_innerRadius.w = u_radius.w - max(b_b, b_l) - u_gap - 0.5;
v_innerRadius.x = v_radius_border.x - max(b_t, b_l) - u_gap - 0.5;
v_innerRadius.y = v_radius_border.y - max(b_t, b_r) - u_gap - 0.5;
v_innerRadius.z = v_radius_border.z - max(b_b, b_r) - u_gap - 0.5;
v_innerRadius.w = v_radius_border.w - max(b_b, b_l) - u_gap - 0.5;

v_innerSize.x = u_dimensions.x - (b_l + b_r) - gap_x2 - 1.0;
v_innerSize.y = u_dimensions.y - (b_t + b_b) - gap_x2 - 1.0;
v_innerSize.x = rect_border.x - (b_l + b_r) - gap_x2 - 1.0;
v_innerSize.y = rect_border.y - (b_t + b_b) - gap_x2 - 1.0;
v_innerSize *= 0.5;
}

Expand Down Expand Up @@ -407,8 +371,6 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
uniform sampler2D u_texture;

/* Passed by shader setup */
uniform vec4 u_radius;

uniform vec4 u_border;
uniform vec4 u_borderColor;
uniform bool u_inset;
Expand All @@ -424,6 +386,7 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
varying vec2 v_halfDimensions;
varying vec4 v_innerRadius;
varying vec2 v_innerSize;
varying vec4 v_radius_border;

float roundedBox(vec2 p, vec2 s, vec4 r) {
r.xy = (p.x > 0.0) ? r.yz : r.xw;
Expand All @@ -435,8 +398,8 @@ export const defaultShaderRoundedWithBorder: ShaderRoundedWithBorder = {
void main() {
vec4 contentTexColor = texture2D(u_texture, v_texcoords) * v_color;

vec2 boxUv = v_nodeCoords.xy * u_dimensions - v_halfDimensions;
float outerShapeDist = roundedBox(boxUv, v_halfDimensions, u_radius);
vec2 boxUv = v_nodeCoords.xy * (v_halfDimensions * 2.0) - v_halfDimensions;
float outerShapeDist = roundedBox(boxUv, v_halfDimensions, v_radius_border);
float outerShapeAlpha = 1.0 - smoothstep(0.0, 1.0, outerShapeDist); // 1 inside, 0 outside

if (u_borderZero) { // No border, effectively no gap from border logic
Expand Down