diff --git a/src/shaders.ts b/src/shaders.ts index 6e6f4b3..3ab5539 100644 --- a/src/shaders.ts +++ b/src/shaders.ts @@ -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]; @@ -209,66 +188,22 @@ const roundedWithBorderProps: lngr.ShaderProps = { 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 @@ -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; @@ -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); @@ -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) { @@ -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; @@ -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; } @@ -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; @@ -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; @@ -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