blob: fe48c767958e6c82a2ab3dc6d0593cfa03df5c9b [file]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
precision mediump float;
#include <impeller/color.glsl>
#include <impeller/types.glsl>
#include "sdf_functions.glsl"
#include "sdf_utils.glsl"
uniform FragInfo {
vec4 color;
vec2 center;
vec2 size;
float stroke_width;
float stroked;
vec4 superellipse_degrees_top;
vec4 superellipse_degrees_right;
vec4 superellipse_semi_axes_top;
vec4 superellipse_semi_axes_right;
vec4 angle_spans_top;
vec4 angle_spans_right;
vec4 octant_offsets_c;
vec4 radii_width;
vec4 radii_height;
vec4 circle_centers_top_x;
vec4 circle_centers_top_y;
vec4 circle_centers_right_x;
vec4 circle_centers_right_y;
vec4 superellipse_scales_x;
vec4 superellipse_scales_y;
vec4 quadrant_centers_x;
vec4 quadrant_centers_y;
vec4 quadrant_splits;
}
frag_info;
out vec4 frag_color;
highp in vec2 v_position;
float distanceFromCircle(vec2 p, float radius) {
return length(p) - radius;
}
float getQuadrantDistance(vec2 p,
float se_degree_top,
float se_degree_right,
float se_a_top,
float se_a_right,
float angle_span_top,
float angle_span_right,
float c,
float radius_top,
float radius_right,
vec2 circle_center_top,
vec2 circle_center_right,
vec2 scale,
vec2 q_center,
int quadrant_index) {
vec2 q_sign = vec2(1.0);
if (quadrant_index == 0)
q_sign = vec2(1.0, 1.0);
else if (quadrant_index == 1)
q_sign = vec2(1.0, -1.0);
else if (quadrant_index == 2)
q_sign = vec2(-1.0, 1.0);
else
q_sign = vec2(-1.0, -1.0);
// Transform the point into the quadrant's local space
vec2 p_local = (p - q_center) * q_sign;
// Clamp the point to positive values - this avoids issues with the sdf
// calculations below. This also means that interior distances for this
// function are not totally accurate.
vec2 p_clamped = max(p_local, 0.0);
// For points that we clamped, return an approximate interior distance
vec2 extents = vec2(scale.x * se_a_top, scale.y * se_a_right);
vec2 d_rect = p_local - extents;
float dist_rect_local =
length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0);
if (p_local.x <= 0.0 || p_local.y <= 0.0) {
return dist_rect_local;
}
// Map p in to a square.
vec2 p_norm = p_clamped / scale;
// Declare all RSE params for a single octant.
float se_degree;
float span;
float radius;
vec2 circle_center;
float axis_length;
// 'p_norm' in the coordinate system of the octant.
vec2 p_oct;
// We split the quadrant along the diagonal of the transition (p_norm.y + c ==
// p_norm.x). This allows us to grab the correct set of parameters for the
// "top" and "right" halves of the corner.
if (p_norm.y + c > p_norm.x) {
p_oct = p_norm + vec2(0.0, c);
se_degree = se_degree_top;
span = angle_span_top;
radius = radius_top;
circle_center = circle_center_top;
axis_length = se_a_top;
} else {
p_oct = p_norm.yx - vec2(0.0, c);
se_degree = se_degree_right;
span = angle_span_right;
radius = radius_right;
circle_center = circle_center_right;
axis_length = se_a_right;
}
// Move the point to the corner circle's coordinate system.
vec2 p_rel = p_oct - circle_center;
// Grab the angle offset of the point.
float theta = atan(p_rel.y, p_rel.x);
// The angular distance between the point and the 45 degree midline.
float d_theta = theta - PI_OVER_FOUR;
d_theta = mod(d_theta + PI, TWO_PI) - PI;
float dist_raw;
vec2 grad_oct;
// If the point is within the span of the corner circle's arc,
// use a circle SDF.
// This works because the normals of the circular and superelliptical sections
// agree at the transition angle, the total RSE curve is continuous and
// the closest point on a continuous curve to a point lies along the normal.
// We also compute the gradient of the distance function for normalization.
if (abs(d_theta) < abs(span)) {
dist_raw = distanceFromCircle(p_rel, radius);
grad_oct = normalize(p_rel);
} else {
dist_raw = sdSuperellipse(p_oct / axis_length, se_degree) * axis_length;
// Clamp the coordinate to avoid division by zero
vec2 p_oct_clamped = max(p_oct, vec2(0.001));
float max_p = max(p_oct_clamped.x, p_oct_clamped.y);
vec2 p_safe = p_oct_clamped / max_p;
// Approximation of the gradient
grad_oct = normalize(pow(p_safe, vec2(se_degree - 1.0)));
}
if (p_norm.y + c <= p_norm.x) {
grad_oct = grad_oct.yx;
}
// Divide the distance by the length of the gradient.
// This ensures that the resulting distance has a gradient magnitude of 1
// everywhere, allowing to be mixed cleanly with other SDFs.
float corner_dist = dist_raw / length(grad_oct / scale);
return max(dist_rect_local, corner_dist);
}
float distanceFromRoundedSuperellipse(vec2 p,
vec4 quadrant_splits,
vec2 size,
vec4 superellipse_degrees_top,
vec4 superellipse_degrees_right,
vec4 superellipse_semi_axes_top,
vec4 superellipse_semi_axes_right,
vec4 angle_spans_top,
vec4 angle_spans_right,
vec4 octant_offsets_c,
vec4 radii_width,
vec4 radii_height,
vec4 circle_centers_top_x,
vec4 circle_centers_top_y,
vec4 circle_centers_right_x,
vec4 circle_centers_right_y,
vec4 superellipse_scales_x,
vec4 superellipse_scales_y,
vec4 quadrant_centers_x,
vec4 quadrant_centers_y) {
vec2 T = vec2(quadrant_splits.x, -size.y);
vec2 R = vec2(size.x, quadrant_splits.w);
vec2 B = vec2(quadrant_splits.y, size.y);
vec2 L = vec2(-size.x, quadrant_splits.z);
// Grab the 2d cross products between p and the split points.
// Imagine drawing a line L from the center of the shape to each split point,
// p x L tells us whether p is clockwise or counterclockwise relative to L.
float cT = T.x * p.y - T.y * p.x;
float cR = R.x * p.y - R.y * p.x;
float cB = B.x * p.y - B.y * p.x;
float cL = L.x * p.y - L.y * p.x;
int quadrant_index = 0;
// cR = p x R <= 0 -> p is counterclockwise relative to R.
// cT = p x T > 0 -> p is clockwise relative to T.
// If p is clockwise relative to T and counterclockwise relative to R,
// p must lie in the TR quadrant.
// If cT = p x T == 0, p is parallel to T, which can misidentify points in the
// BR quadrant.
if ((cR < 0.0 || cR == 0.0 && p.x > 0.0) &&
(cT > 0.0 || cT == 0.0 && p.x > 0.0)) {
quadrant_index = 1; // TR
} else if ((cB < 0.0 || cB == 0.0 && p.x > 0.0) &&
(cR > 0.0 || cR == 0.0 && p.x > 0.0)) {
quadrant_index = 0; // BR
} else if (cB >= 0.0 && cL <= 0.0) {
quadrant_index = 2; // BL
} else {
quadrant_index = 3; // TL
}
float se_degree_top = superellipse_degrees_top[quadrant_index];
float se_degree_right = superellipse_degrees_right[quadrant_index];
float se_a_top = superellipse_semi_axes_top[quadrant_index];
float se_a_right = superellipse_semi_axes_right[quadrant_index];
float angle_span_top = angle_spans_top[quadrant_index];
float angle_span_right = angle_spans_right[quadrant_index];
float c = octant_offsets_c[quadrant_index];
float radius_top = radii_width[quadrant_index];
float radius_right = radii_height[quadrant_index];
vec2 circle_center_top = vec2(circle_centers_top_x[quadrant_index],
circle_centers_top_y[quadrant_index]);
vec2 circle_center_right = vec2(circle_centers_right_x[quadrant_index],
circle_centers_right_y[quadrant_index]);
vec2 scale = vec2(superellipse_scales_x[quadrant_index],
superellipse_scales_y[quadrant_index]);
vec2 q_center = vec2(quadrant_centers_x[quadrant_index],
quadrant_centers_y[quadrant_index]);
return getQuadrantDistance(
p, se_degree_top, se_degree_right, se_a_top, se_a_right, angle_span_top,
angle_span_right, c, radius_top, radius_right, circle_center_top,
circle_center_right, scale, q_center, quadrant_index);
}
float pixelSize(float sdf) {
vec2 gradient = vec2(dFdx(sdf), dFdy(sdf));
return length(gradient);
}
void main() {
vec2 p = v_position - frag_info.center;
float base_sdf = distanceFromRoundedSuperellipse(
p, frag_info.quadrant_splits, frag_info.size,
frag_info.superellipse_degrees_top, frag_info.superellipse_degrees_right,
frag_info.superellipse_semi_axes_top,
frag_info.superellipse_semi_axes_right, frag_info.angle_spans_top,
frag_info.angle_spans_right, frag_info.octant_offsets_c,
frag_info.radii_width, frag_info.radii_height,
frag_info.circle_centers_top_x, frag_info.circle_centers_top_y,
frag_info.circle_centers_right_x, frag_info.circle_centers_right_y,
frag_info.superellipse_scales_x, frag_info.superellipse_scales_y,
frag_info.quadrant_centers_x, frag_info.quadrant_centers_y);
float base_pixel_size = pixelSize(base_sdf);
vec2 sdf_and_pixel_size =
(frag_info.stroked < 0.5)
? vec2(base_sdf, base_pixel_size)
: SDFStroke(base_sdf, base_pixel_size, frag_info.stroke_width);
float sdf = sdf_and_pixel_size.x;
float pixel_size = sdf_and_pixel_size.y;
float alpha = SDFAlpha(sdf, pixel_size, 1.0);
frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha);
frag_color = IPPremultiply(frag_color);
}