From 436b662850b4eda82f3df957dace502ebfdb8c66 Mon Sep 17 00:00:00 2001 From: Yein Jo Date: Thu, 21 Jul 2022 16:19:51 +0000 Subject: [PATCH] Add ellipse shape to the ripple. Bug: b/203800342 Test: Manual, RippleViewTest Change-Id: I29fe4d05a868305814a980ef2bde53a608df0ce2 --- .../receiver/ReceiverChipRippleView.kt | 1 + .../android/systemui/ripple/RippleShader.kt | 48 ++++++++--- .../ripple/RippleShaderUtilLibrary.kt | 31 +++---- .../systemui/ripple/SdfShaderLibrary.kt | 86 +++++++++++++------ .../android/systemui/ripple/RippleViewTest.kt | 5 ++ 5 files changed, 118 insertions(+), 53 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index 8fd4440fe7a51..6a505f06a4959 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index f4b94ede3bd9e..0a8e6e21d5b36 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt @@ -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) diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt index 2dbc0ba2d8f59..0cacbc2819c53 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt @@ -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; - }""" + }""" } } diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt index 4360105375ccf..7f26146f541dc 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt @@ -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); } """ } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt index f247c651cbca8..2d2f4cc9edd25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt @@ -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) + } }