ellipse.glsl (3519B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Preprocess the radii for computing the distance approximation. This should 6 // be used in the vertex shader if possible to avoid doing expensive division 7 // in the fragment shader. When dealing with a point (zero radii), approximate 8 // it as an ellipse with very small radii so that we don't need to branch. 9 vec2 inverse_radii_squared(vec2 radii) { 10 return 1.0 / max(radii * radii, 1.0e-6); 11 } 12 13 #ifdef WR_FRAGMENT_SHADER 14 15 // One iteration of Newton's method on the 2D equation of an ellipse: 16 // 17 // E(x, y) = x^2/a^2 + y^2/b^2 - 1 18 // 19 // The Jacobian of this equation is: 20 // 21 // J(E(x, y)) = [ 2*x/a^2 2*y/b^2 ] 22 // 23 // We approximate the distance with: 24 // 25 // E(x, y) / ||J(E(x, y))|| 26 // 27 // See G. Taubin, "Distance Approximations for Rasterizing Implicit 28 // Curves", section 3. 29 // 30 // A scale relative to the unit scale of the ellipse may be passed in to cause 31 // the math to degenerate to length(p) when scale is 0, or otherwise give the 32 // normal distance approximation if scale is 1. 33 float distance_to_ellipse_approx(vec2 p, vec2 inv_radii_sq, float scale) { 34 vec2 p_r = p * inv_radii_sq; 35 float g = dot(p, p_r) - scale; 36 vec2 dG = (1.0 + scale) * p_r; 37 return g * inversesqrt(dot(dG, dG)); 38 } 39 40 // Slower but more accurate version that uses the exact distance when dealing 41 // with a 0-radius point distance and otherwise uses the faster approximation 42 // when dealing with non-zero radii. 43 float distance_to_ellipse(vec2 p, vec2 radii) { 44 return distance_to_ellipse_approx(p, inverse_radii_squared(radii), 45 float(all(greaterThan(radii, vec2(0.0))))); 46 } 47 48 float distance_to_rounded_rect( 49 vec2 pos, 50 vec3 plane_tl, 51 vec4 center_radius_tl, 52 vec3 plane_tr, 53 vec4 center_radius_tr, 54 vec3 plane_br, 55 vec4 center_radius_br, 56 vec3 plane_bl, 57 vec4 center_radius_bl, 58 vec4 rect_bounds 59 ) { 60 // Clip against each ellipse. If the fragment is in a corner, one of the 61 // branches below will select it as the corner to calculate the distance 62 // to. We use half-space planes to detect which corner's ellipse the 63 // fragment is inside, where the plane is defined by a normal and offset. 64 // If outside any ellipse, default to a small offset so a negative distance 65 // is returned for it. 66 vec4 corner = vec4(vec2(1.0e-6), vec2(1.0)); 67 68 // Calculate the ellipse parameters for each corner. 69 center_radius_tl.xy = center_radius_tl.xy - pos; 70 center_radius_tr.xy = (center_radius_tr.xy - pos) * vec2(-1.0, 1.0); 71 center_radius_br.xy = pos - center_radius_br.xy; 72 center_radius_bl.xy = (center_radius_bl.xy - pos) * vec2(1.0, -1.0); 73 74 // Evaluate each half-space plane in turn to select a corner. 75 if (dot(pos, plane_tl.xy) > plane_tl.z) { 76 corner = center_radius_tl; 77 } 78 if (dot(pos, plane_tr.xy) > plane_tr.z) { 79 corner = center_radius_tr; 80 } 81 if (dot(pos, plane_br.xy) > plane_br.z) { 82 corner = center_radius_br; 83 } 84 if (dot(pos, plane_bl.xy) > plane_bl.z) { 85 corner = center_radius_bl; 86 } 87 88 // Calculate the distance of the selected corner and the rectangle bounds, 89 // whichever is greater. 90 return max(distance_to_ellipse_approx(corner.xy, corner.zw, 1.0), 91 signed_distance_rect(pos, rect_bounds.xy, rect_bounds.zw)); 92 } 93 #endif