Add ellipse shape to the ripple.

Bug: b/203800342
Test: Manual, RippleViewTest
Change-Id: I29fe4d05a868305814a980ef2bde53a608df0ce2
This commit is contained in:
Yein Jo
2022-07-21 16:19:51 +00:00
parent a37c26f550
commit 436b662850
5 changed files with 118 additions and 53 deletions

View File

@@ -25,6 +25,7 @@ import com.android.systemui.ripple.RippleView
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
// TODO: use RippleShape#ELLIPSE when calling setupShader.
setupShader()
setRippleFill(true)
duration = 3000L

View File

@@ -36,7 +36,8 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
/** Shapes that the [RippleShader] supports. */
enum class RippleShape {
CIRCLE,
ROUNDED_BOX
ROUNDED_BOX,
ELLIPSE
}
companion object {
@@ -57,12 +58,10 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
uniform float in_sparkle_strength;"""
private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
// distortion only applies to circle
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float radius = in_size.x * 0.5;
float sparkleRing = softRing(p_distorted, in_center, radius, in_blur);
float inside = softCircle(p_distorted, in_center, radius * 1.2, in_blur);
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
@@ -75,9 +74,26 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
"""
private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
float sparkleRing = softRoundedBoxRing(p, in_center, in_size, in_cornerRadius,
in_thickness, in_blur);
float inside = softRoundedBox(p, in_center, in_size * 1.2, in_cornerRadius, in_blur);
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
in_thickness), in_blur);
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
"""
private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
@@ -90,13 +106,21 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
"""
private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
SdfShaderLibrary.CIRCLE_SDF + SHADER_CIRCLE_MAIN
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
SHADER_CIRCLE_MAIN
private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.ROUNDED_BOX_SDF +
SHADER_ROUNDED_BOX_MAIN
RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
SHADER_ELLIPSE_MAIN
private fun buildShader(rippleShape: RippleShape): String =
if (rippleShape == RippleShape.CIRCLE) CIRCLE_SHADER else ROUNDED_BOX_SHADER
when (rippleShape) {
RippleShape.CIRCLE -> CIRCLE_SHADER
RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
RippleShape.ELLIPSE -> ELLIPSE_SHADER
}
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)

View File

@@ -18,7 +18,8 @@ package com.android.systemui.ripple
/** A common utility functions that are used for computing [RippleShader]. */
class RippleShaderUtilLibrary {
companion object {
const val SHADER_LIB = """float triangleNoise(vec2 n) {
const val SHADER_LIB = """
float triangleNoise(vec2 n) {
n = fract(n * vec2(5.3987, 5.4421));
n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
float xy = n.x * n.y;
@@ -27,25 +28,25 @@ class RippleShaderUtilLibrary {
const float PI = 3.1415926535897932384626;
float sparkles(vec2 uv, float t) {
float n = triangleNoise(uv);
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
float l = i * 0.01;
float h = l + 0.1;
float o = smoothstep(n - l, h, n);
o *= abs(sin(PI * o * (t + 0.55 * i)));
s += o;
}
return s;
float n = triangleNoise(uv);
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
float l = i * 0.01;
float h = l + 0.1;
float o = smoothstep(n - l, h, n);
o *= abs(sin(PI * o * (t + 0.55 * i)));
s += o;
}
return s;
}
vec2 distort(vec2 p, float time, float distort_amount_radial,
float distort_amount_xy) {
float angle = atan(p.y, p.x);
return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
float distort_amount_xy) {
float angle = atan(p.y, p.x);
return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
}"""
}"""
}
}

View File

@@ -23,22 +23,13 @@ class SdfShaderLibrary {
return (length(p)-r) / r;
}
float softCircle(vec2 p, vec2 origin, float radius, float blur) {
float d = sdCircle(p-origin, radius);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
}
float softRing(vec2 p, vec2 origin, float radius, float blur) {
float circleRing(vec2 p, float radius) {
float thicknessHalf = radius * 0.25;
float outerCircle = sdCircle(p-origin, radius + thicknessHalf);
float innerCircle = sdCircle(p-origin, radius);
float outerCircle = sdCircle(p, radius + thicknessHalf);
float innerCircle = sdCircle(p, radius);
float d = max(outerCircle, -innerCircle);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
return subtract(outerCircle, innerCircle);
}
"""
@@ -54,23 +45,66 @@ class SdfShaderLibrary {
return (outside+inside-cornerRadius)/size.y;
}
float softRoundedBox(vec2 p, vec2 origin, vec2 size, float cornerRadius, float blur) {
float d = sdRoundedBox(p-origin, size, cornerRadius);
float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
float borderThickness) {
float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
cornerRadius - borderThickness);
return subtract(outerRoundBox, innerRoundBox);
}
"""
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
// possible.
const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
wh *= 0.5;
// symmetry
(wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
vec2 u = wh*p, v = wh*wh;
float U1 = u.y/2.0; float U5 = 4.0*U1;
float U2 = v.y-v.x; float U6 = 6.0*U1;
float U3 = u.x-U2; float U7 = 3.0*U3;
float U4 = u.x+U2;
float t = 0.5;
for (int i = 0; i < 3; i ++) {
float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
float F2 = t*t*(U5*t+U7)+U4;
float F3 = t*(U6*t+U7);
t += (F1*F2)/(F1*F3-F2*F2);
}
t = clamp(t, 0.0, 1.0);
float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
d /= wh.y;
return (dot(p/wh,p/wh)>1.0) ? d : -d;
}
float ellipseRing(vec2 p, vec2 wh) {
vec2 thicknessHalf = wh * 0.25;
float outerEllipse = sdEllipse(p, wh + thicknessHalf);
float innerEllipse = sdEllipse(p, wh);
return subtract(outerEllipse, innerEllipse);
}
"""
const val SHADER_SDF_OPERATION_LIB = """
float soften(float d, float blur) {
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
}
float softRoundedBoxRing(vec2 p, vec2 origin, vec2 size, float cornerRadius,
float borderThickness, float blur) {
float outerRoundBox = sdRoundedBox(p-origin, size, cornerRadius);
float innerRoundBox = sdRoundedBox(p-origin, size - vec2(borderThickness),
cornerRadius - borderThickness);
float d = max(outerRoundBox, -innerRoundBox);
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
float subtract(float outer, float inner) {
return max(outer, -inner);
}
"""
}

View File

@@ -43,4 +43,9 @@ class RippleViewTest : SysuiTestCase() {
fun testSetupShader_compilesRoundedBox() {
rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
}
@Test
fun testSetupShader_compilesEllipse() {
rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
}
}