From 2892727037309e4926b722d0aa5ba66b4b3ade11 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 8 Sep 2020 16:10:14 -0700 Subject: [PATCH] Add gyro effect to glass prototype Test: manual Change-Id: I108d488e3825d075f808b2996ea6ba5ab9fcde9a --- tests/SilkFX/res/layout/activity_glass.xml | 17 ++- .../test/silkfx/materials/GlassActivity.kt | 8 ++ .../test/silkfx/materials/GlassView.kt | 100 ++++++++++++++++-- 3 files changed, 111 insertions(+), 14 deletions(-) diff --git a/tests/SilkFX/res/layout/activity_glass.xml b/tests/SilkFX/res/layout/activity_glass.xml index 7af68a9acbe61..aa09f276d5c89 100644 --- a/tests/SilkFX/res/layout/activity_glass.xml +++ b/tests/SilkFX/res/layout/activity_glass.xml @@ -41,7 +41,16 @@ app:layout_constraintBottom_toTopOf="@+id/bottomPanel" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent"> + + diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt index 85b2bdaa33887..dde245ff9baf0 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt @@ -47,6 +47,7 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { lateinit var scrimOpacityValue: TextView lateinit var blurRadiusValue: TextView lateinit var zoomValue: TextView + lateinit var textOverlay: TextView var background: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) set(value) { @@ -74,6 +75,7 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { scrimOpacityValue = requireViewById(R.id.scrimOpacityValue) blurRadiusValue = requireViewById(R.id.blurRadiusValue) zoomValue = requireViewById(R.id.zoomValue) + textOverlay = requireViewById(R.id.textOverlay) background = BitmapFactory.decodeResource(resources, R.drawable.background1) @@ -90,6 +92,7 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { lightMaterialSwitch.setOnCheckedChangeListener { _, isChecked -> materialView.color = if (isChecked) Color.WHITE else Color.BLACK + textOverlay.setTextColor(if (isChecked) Color.BLACK else Color.WHITE) } } @@ -120,6 +123,11 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { } } + override fun onStop() { + super.onStop() + materialView.resetGyroOffsets() + } + override fun onStartTrackingTouch(seekBar: SeekBar?) {} override fun onStopTrackingTouch(seekBar: SeekBar?) {} diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt index 60797680b79ef..711758476a62f 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt @@ -25,23 +25,83 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline import android.graphics.Paint +import android.graphics.RadialGradient import android.graphics.Rect import android.graphics.Shader +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import com.android.internal.graphics.ColorUtils import com.android.test.silkfx.R +import kotlin.math.sin +import kotlin.math.sqrt -class GlassView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) { +class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) { - var noise = BitmapFactory.decodeResource(resources, R.drawable.noise) - var materialPaint = Paint() - var scrimPaint = Paint() - var noisePaint = Paint() - var blurPaint = Paint() + private val textureTranslationMultiplier = 200f - val src = Rect() - val dst = Rect() + private var gyroXRotation = 0f + private var gyroYRotation = 0f + + private var noise = BitmapFactory.decodeResource(resources, R.drawable.noise) + private var materialPaint = Paint() + private var scrimPaint = Paint() + private var noisePaint = Paint() + private var blurPaint = Paint() + + private val src = Rect() + private val dst = Rect() + + private val sensorManager = context.getSystemService(SensorManager::class.java) + private val sensorListener = object : SensorEventListener { + + // Constant to convert nanoseconds to seconds. + private val NS2S = 1.0f / 1000000000.0f + private val EPSILON = 0.000001f + private var timestamp: Float = 0f + + override fun onSensorChanged(event: SensorEvent?) { + // This timestep's delta rotation to be multiplied by the current rotation + // after computing it from the gyro sample data. + if (timestamp != 0f && event != null) { + val dT = (event.timestamp - timestamp) * NS2S + // Axis of the rotation sample, not normalized yet. + var axisX: Float = event.values[0] + var axisY: Float = event.values[1] + var axisZ: Float = event.values[2] + + // Calculate the angular speed of the sample + val omegaMagnitude: Float = sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ) + + // Normalize the rotation vector if it's big enough to get the axis + // (that is, EPSILON should represent your maximum allowable margin of error) + if (omegaMagnitude > EPSILON) { + axisX /= omegaMagnitude + axisY /= omegaMagnitude + axisZ /= omegaMagnitude + } + + // Integrate around this axis with the angular speed by the timestep + // in order to get a delta rotation from this sample over the timestep + // We will convert this axis-angle representation of the delta rotation + // into a quaternion before turning it into the rotation matrix. + val thetaOverTwo: Float = omegaMagnitude * dT / 2.0f + val sinThetaOverTwo: Float = sin(thetaOverTwo) + gyroXRotation += sinThetaOverTwo * axisX + gyroYRotation += sinThetaOverTwo * axisY + + invalidate() + } + timestamp = event?.timestamp?.toFloat() ?: 0f + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { } + } var backgroundBitmap: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) set(value) { @@ -97,6 +157,7 @@ class GlassView(context: Context, attributeSet: AttributeSet) : View(context, at } init { + setWillNotDraw(false) materialPaint.blendMode = BlendMode.SOFT_LIGHT noisePaint.blendMode = BlendMode.SOFT_LIGHT noisePaint.shader = BitmapShader(noise, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) @@ -112,12 +173,25 @@ class GlassView(context: Context, attributeSet: AttributeSet) : View(context, at clipToOutline = true } + override fun onAttachedToWindow() { + sensorManager?.getSensorList(Sensor.TYPE_GYROSCOPE)?.firstOrNull().let { + sensorManager?.registerListener(sensorListener, it, SensorManager.SENSOR_DELAY_GAME) + } + } + + override fun onDetachedFromWindow() { + sensorManager?.unregisterListener(sensorListener) + } + override fun onDraw(canvas: Canvas?) { - src.set(-width/2, -height/2, width/2, height/2) + src.set(-width / 2, -height / 2, width / 2, height / 2) src.scale(1.0f + zoom) val centerX = left + width / 2 val centerY = top + height / 2 - src.set(src.left + centerX, src.top + centerY, src.right + centerX, src.bottom + centerY) + val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt() + val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt() + src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset, + src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset) dst.set(0, 0, width, height) canvas?.drawBitmap(backgroundBitmap, src, dst, blurPaint) @@ -125,4 +199,10 @@ class GlassView(context: Context, attributeSet: AttributeSet) : View(context, at canvas?.drawRect(dst, noisePaint) canvas?.drawRect(dst, scrimPaint) } + + fun resetGyroOffsets() { + gyroXRotation = 0f + gyroYRotation = 0f + invalidate() + } } \ No newline at end of file