Skip to content

Commit a451fb8

Browse files
committed
Extract Motion Controls
1 parent 1a87842 commit a451fb8

2 files changed

Lines changed: 158 additions & 131 deletions

File tree

‎app/src/main/java/io/github/kitswas/virtualgamepadmobile/ui/screens/Gamepad.kt‎

Lines changed: 8 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
package io.github.kitswas.virtualgamepadmobile.ui.screens
22

33
import android.annotation.SuppressLint
4-
import android.content.Context
54
import android.content.res.Configuration
6-
import android.hardware.Sensor
7-
import android.hardware.SensorEvent
8-
import android.hardware.SensorEventListener
9-
import android.hardware.SensorManager
105
import android.util.Log
11-
import android.view.Surface
126
import android.widget.Toast
137
import androidx.compose.runtime.Composable
14-
import androidx.compose.runtime.DisposableEffect
158
import androidx.compose.runtime.LaunchedEffect
169
import androidx.compose.runtime.collectAsState
1710
import androidx.compose.runtime.getValue
@@ -37,26 +30,14 @@ import io.github.kitswas.virtualgamepadmobile.data.defaultMotionStickControl
3730
import io.github.kitswas.virtualgamepadmobile.data.defaultPollingDelay
3831
import io.github.kitswas.virtualgamepadmobile.network.ConnectionViewModel
3932
import io.github.kitswas.virtualgamepadmobile.ui.composables.DrawGamepad
33+
import io.github.kitswas.virtualgamepadmobile.ui.utils.BindMotionStickControl
4034
import io.github.kitswas.virtualgamepadmobile.ui.utils.findActivity
4135
import kotlinx.coroutines.CoroutineScope
4236
import kotlinx.coroutines.Dispatchers
4337
import kotlinx.coroutines.delay
4438
import kotlinx.coroutines.launch
45-
import kotlin.math.PI
4639

4740
private const val tag = "GamePadScreen"
48-
private const val maxTiltDegrees = 30f
49-
private const val gravityAlpha = 0.8f
50-
51-
private fun toStickValue(angleRad: Float, sensitivity: Float): Float {
52-
val maxTiltRadians = (maxTiltDegrees / 180f * PI).toFloat()
53-
return (angleRad / maxTiltRadians * sensitivity).coerceIn(-1f, 1f)
54-
}
55-
56-
private fun toStickValueFromGravity(gravityComponent: Float, sensitivity: Float): Float {
57-
val normalized = (gravityComponent / SensorManager.GRAVITY_EARTH).coerceIn(-1f, 1f)
58-
return (normalized * sensitivity).coerceIn(-1f, 1f)
59-
}
6041

6142
@SuppressLint("ConfigurationScreenWidthHeight")
6243
@Composable
@@ -96,117 +77,13 @@ fun GamePad(
9677
motionStickY = motionStickY.value,
9778
)
9879

99-
val sensorManager = remember {
100-
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
101-
}
102-
val motionSensor = remember {
103-
sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)
104-
?: sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
105-
?: sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
106-
}
107-
val displayRotation =
108-
LocalContext.current.findActivity()?.display?.rotation ?: Surface.ROTATION_0
109-
110-
DisposableEffect(motionStickControl, motionSensitivity, motionSensor, displayRotation) {
111-
if (motionStickControl == MotionStickControl.OFF || motionSensor == null) {
112-
motionStickX.value = 0f
113-
motionStickY.value = 0f
114-
if (motionStickControl == MotionStickControl.LEFT) {
115-
gamepadState.LeftThumbstickX = 0f
116-
gamepadState.LeftThumbstickY = 0f
117-
}
118-
if (motionStickControl == MotionStickControl.RIGHT) {
119-
gamepadState.RightThumbstickX = 0f
120-
gamepadState.RightThumbstickY = 0f
121-
}
122-
return@DisposableEffect onDispose { }
123-
}
124-
125-
val rotationMatrix = FloatArray(9)
126-
val remappedRotationMatrix = FloatArray(9)
127-
val orientation = FloatArray(3)
128-
val gravity = FloatArray(3)
129-
130-
val axisConfig = when (displayRotation) {
131-
Surface.ROTATION_90 -> SensorManager.AXIS_Z to SensorManager.AXIS_MINUS_X
132-
Surface.ROTATION_180 -> SensorManager.AXIS_MINUS_X to SensorManager.AXIS_MINUS_Z
133-
Surface.ROTATION_270 -> SensorManager.AXIS_MINUS_Z to SensorManager.AXIS_X
134-
else -> SensorManager.AXIS_X to SensorManager.AXIS_Z
135-
}
136-
137-
fun applyStickValues(stickX: Float, stickY: Float) {
138-
motionStickX.value = stickX
139-
motionStickY.value = stickY
140-
when (motionStickControl) {
141-
MotionStickControl.LEFT -> {
142-
gamepadState.LeftThumbstickX = stickX
143-
gamepadState.LeftThumbstickY = stickY
144-
}
145-
146-
MotionStickControl.RIGHT -> {
147-
gamepadState.RightThumbstickX = stickX
148-
gamepadState.RightThumbstickY = stickY
149-
}
150-
151-
MotionStickControl.OFF -> Unit
152-
}
153-
}
154-
155-
val listener = object : SensorEventListener {
156-
override fun onSensorChanged(event: SensorEvent) {
157-
if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
158-
gravity[0] = gravityAlpha * gravity[0] + (1f - gravityAlpha) * event.values[0]
159-
gravity[1] = gravityAlpha * gravity[1] + (1f - gravityAlpha) * event.values[1]
160-
161-
val (rawX, rawY) = when (displayRotation) {
162-
Surface.ROTATION_90 -> gravity[1] to gravity[0]
163-
Surface.ROTATION_180 -> gravity[0] to -gravity[1]
164-
Surface.ROTATION_270 -> -gravity[1] to -gravity[0]
165-
else -> -gravity[0] to gravity[1]
166-
}
167-
applyStickValues(
168-
stickX = toStickValueFromGravity(rawX, motionSensitivity),
169-
stickY = toStickValueFromGravity(rawY, motionSensitivity),
170-
)
171-
return
172-
}
173-
174-
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
175-
SensorManager.remapCoordinateSystem(
176-
rotationMatrix,
177-
axisConfig.first,
178-
axisConfig.second,
179-
remappedRotationMatrix,
180-
)
181-
SensorManager.getOrientation(remappedRotationMatrix, orientation)
182-
183-
val pitch = orientation[1]
184-
val roll = orientation[2]
185-
applyStickValues(
186-
stickX = toStickValue(-roll, motionSensitivity),
187-
stickY = toStickValue(pitch, motionSensitivity),
188-
)
189-
}
190-
191-
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
192-
}
193-
194-
sensorManager.registerListener(listener, motionSensor, SensorManager.SENSOR_DELAY_GAME)
195-
196-
onDispose {
197-
sensorManager.unregisterListener(listener)
198-
motionStickX.value = 0f
199-
motionStickY.value = 0f
200-
if (motionStickControl == MotionStickControl.LEFT) {
201-
gamepadState.LeftThumbstickX = 0f
202-
gamepadState.LeftThumbstickY = 0f
203-
}
204-
if (motionStickControl == MotionStickControl.RIGHT) {
205-
gamepadState.RightThumbstickX = 0f
206-
gamepadState.RightThumbstickY = 0f
207-
}
208-
}
209-
}
80+
BindMotionStickControl(
81+
gamepadState = gamepadState,
82+
motionStickControl = motionStickControl,
83+
motionSensitivity = motionSensitivity,
84+
motionStickX = motionStickX,
85+
motionStickY = motionStickY,
86+
)
21087

21188
val activity = LocalContext.current.findActivity()
21289
// disconnect on back press
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package io.github.kitswas.virtualgamepadmobile.ui.utils
2+
3+
import android.content.Context
4+
import android.hardware.Sensor
5+
import android.hardware.SensorEvent
6+
import android.hardware.SensorEventListener
7+
import android.hardware.SensorManager
8+
import android.view.Surface
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.DisposableEffect
11+
import androidx.compose.runtime.MutableState
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.ui.platform.LocalContext
14+
import io.github.kitswas.VGP_Data_Exchange.GamepadReading
15+
import io.github.kitswas.virtualgamepadmobile.data.MotionStickControl
16+
import kotlin.math.PI
17+
18+
private const val maxTiltDegrees = 30f
19+
private const val gravityAlpha = 0.8f
20+
21+
private fun toStickValue(angleRad: Float, sensitivity: Float): Float {
22+
val maxTiltRadians = (maxTiltDegrees / 180f * PI).toFloat()
23+
return (angleRad / maxTiltRadians * sensitivity).coerceIn(-1f, 1f)
24+
}
25+
26+
private fun toStickValueFromGravity(gravityComponent: Float, sensitivity: Float): Float {
27+
val normalized = (gravityComponent / SensorManager.GRAVITY_EARTH).coerceIn(-1f, 1f)
28+
return (normalized * sensitivity).coerceIn(-1f, 1f)
29+
}
30+
31+
@Composable
32+
fun BindMotionStickControl(
33+
gamepadState: GamepadReading,
34+
motionStickControl: MotionStickControl,
35+
motionSensitivity: Float,
36+
motionStickX: MutableState<Float>,
37+
motionStickY: MutableState<Float>,
38+
) {
39+
val context = LocalContext.current
40+
val sensorManager = remember {
41+
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
42+
}
43+
val motionSensor = remember {
44+
sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)
45+
?: sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
46+
?: sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
47+
}
48+
val displayRotation = context.findActivity()?.display?.rotation ?: Surface.ROTATION_0
49+
50+
DisposableEffect(motionStickControl, motionSensitivity, motionSensor, displayRotation) {
51+
if (motionStickControl == MotionStickControl.OFF || motionSensor == null) {
52+
motionStickX.value = 0f
53+
motionStickY.value = 0f
54+
if (motionStickControl == MotionStickControl.LEFT) {
55+
gamepadState.LeftThumbstickX = 0f
56+
gamepadState.LeftThumbstickY = 0f
57+
}
58+
if (motionStickControl == MotionStickControl.RIGHT) {
59+
gamepadState.RightThumbstickX = 0f
60+
gamepadState.RightThumbstickY = 0f
61+
}
62+
return@DisposableEffect onDispose { }
63+
}
64+
65+
val rotationMatrix = FloatArray(9)
66+
val remappedRotationMatrix = FloatArray(9)
67+
val orientation = FloatArray(3)
68+
val gravity = FloatArray(3)
69+
70+
val axisConfig = when (displayRotation) {
71+
Surface.ROTATION_90 -> SensorManager.AXIS_Z to SensorManager.AXIS_MINUS_X
72+
Surface.ROTATION_180 -> SensorManager.AXIS_MINUS_X to SensorManager.AXIS_MINUS_Z
73+
Surface.ROTATION_270 -> SensorManager.AXIS_MINUS_Z to SensorManager.AXIS_X
74+
else -> SensorManager.AXIS_X to SensorManager.AXIS_Z
75+
}
76+
77+
fun applyStickValues(stickX: Float, stickY: Float) {
78+
motionStickX.value = stickX
79+
motionStickY.value = stickY
80+
when (motionStickControl) {
81+
MotionStickControl.LEFT -> {
82+
gamepadState.LeftThumbstickX = stickX
83+
gamepadState.LeftThumbstickY = stickY
84+
}
85+
86+
MotionStickControl.RIGHT -> {
87+
gamepadState.RightThumbstickX = stickX
88+
gamepadState.RightThumbstickY = stickY
89+
}
90+
91+
MotionStickControl.OFF -> Unit
92+
}
93+
}
94+
95+
val listener = object : SensorEventListener {
96+
override fun onSensorChanged(event: SensorEvent) {
97+
if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
98+
gravity[0] = gravityAlpha * gravity[0] + (1f - gravityAlpha) * event.values[0]
99+
gravity[1] = gravityAlpha * gravity[1] + (1f - gravityAlpha) * event.values[1]
100+
101+
val (rawX, rawY) = when (displayRotation) {
102+
Surface.ROTATION_90 -> gravity[1] to gravity[0]
103+
Surface.ROTATION_180 -> gravity[0] to -gravity[1]
104+
Surface.ROTATION_270 -> -gravity[1] to -gravity[0]
105+
else -> -gravity[0] to gravity[1]
106+
}
107+
applyStickValues(
108+
stickX = toStickValueFromGravity(rawX, motionSensitivity),
109+
stickY = toStickValueFromGravity(rawY, motionSensitivity),
110+
)
111+
return
112+
}
113+
114+
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
115+
SensorManager.remapCoordinateSystem(
116+
rotationMatrix,
117+
axisConfig.first,
118+
axisConfig.second,
119+
remappedRotationMatrix,
120+
)
121+
SensorManager.getOrientation(remappedRotationMatrix, orientation)
122+
123+
val pitch = orientation[1]
124+
val roll = orientation[2]
125+
applyStickValues(
126+
stickX = toStickValue(-roll, motionSensitivity),
127+
stickY = toStickValue(pitch, motionSensitivity),
128+
)
129+
}
130+
131+
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
132+
}
133+
134+
sensorManager.registerListener(listener, motionSensor, SensorManager.SENSOR_DELAY_GAME)
135+
136+
onDispose {
137+
sensorManager.unregisterListener(listener)
138+
motionStickX.value = 0f
139+
motionStickY.value = 0f
140+
if (motionStickControl == MotionStickControl.LEFT) {
141+
gamepadState.LeftThumbstickX = 0f
142+
gamepadState.LeftThumbstickY = 0f
143+
}
144+
if (motionStickControl == MotionStickControl.RIGHT) {
145+
gamepadState.RightThumbstickX = 0f
146+
gamepadState.RightThumbstickY = 0f
147+
}
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)