I am making an game where I need to know if the phone is tilted upward (towards sky) or tilted downwards (towards ground) when in landscape.
I have used this answer Answer Link as my starting point. But when app is in landscape orientation , phone perpendicular to the ground gives value 0 and tilting it upwards or downwards give value 90. Due to this I can't determine if user has tilted phone upwards or downwards.
If anyone wants to know how I am implementing my sensor then he can ask for it but I think this much context would be enough to understand the problem.
This is how I reached the correct solution. First I made an abstract class to be used with multiple sensors (learned from Phillip Lanckner Youtube)
abstract class MeasurableSensor(protected val sensorType : Int) {
protected var onSensorValuesChanged : ((List<Float>) -> Unit)? = null
abstract val doesSensorExist : Boolean
abstract fun startListening()
abstract fun stopListening()
fun setOnSensorValuesChangedListener(listener : ((List<Float>) -> Unit)){
onSensorValuesChanged = listener
}
}
abstract class AndroidSensor(
private val context : Context,
private val sensorFeature : String,
sensorType : Int
) : MeasurableSensor(sensorType) , SensorEventListener{
override val doesSensorExist: Boolean
get() = context.packageManager.hasSystemFeature(sensorFeature)
private lateinit var sensorManager: SensorManager
private var sensor : Sensor? = null
override fun startListening() {
if(!doesSensorExist) return
if(!::sensorManager.isInitialized && sensor == null){
sensorManager = context.getSystemService(SensorManager :: class.java) as SensorManager
sensor = sensorManager.getDefaultSensor(sensorType)
}
sensor?.let {
sensorManager.registerListener(this , it , SensorManager.SENSOR_DELAY_NORMAL)
}
}
override fun stopListening() {
if(!doesSensorExist || !::sensorManager.isInitialized) return
sensorManager.unregisterListener(this)
}
override fun onSensorChanged(event: SensorEvent?) {
if(!doesSensorExist) return
if (event?.sensor?.type == sensorType){
onSensorValuesChanged?.invoke(event.values.toList())
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
}
These are two sensor required to get the exact orientation of phone in 3D plane.
class AccelerometerSensor(
context: Context
) : AndroidSensor(
context,
PackageManager.FEATURE_SENSOR_ACCELEROMETER,
Sensor.TYPE_ACCELEROMETER
)
class MagnetometerSensor(
context: Context
) : AndroidSensor(
context,
PackageManager.FEATURE_SENSOR_ACCELEROMETER,
Sensor.TYPE_MAGNETIC_FIELD
)
Implementation in the viewModel
@HiltViewModel
class ViewModel @Inject constructor(
private val accelerometerSensor : MeasurableSensor,
private val magnetometerSensor : MagnetometerSensor,
) : ViewModel() {
private val _orientation = MutableStateFlow(OrientationState.PERPENDICULAR)
val orientation : StateFlow<OrientationState> get() = _orientation
private val accelerometerReading = FloatArray(3)
private val magnetometerReading = FloatArray(3)
private val rotationMatrix = FloatArray(9)
private val orientationAngles = FloatArray(3)
fun startSensors(){
accelerometerSensor.startListening()
magnetometerSensor.startListening()
initListeners()
}
fun stopSensors(){
accelerometerSensor.stopListening()
magnetometerSensor.stopListening()
}
private fun initListeners() {
accelerometerSensor.setOnSensorValuesChangedListener { accelerometerReading ->
this.accelerometerReading[0] = accelerometerReading[0]
this.accelerometerReading[1] = accelerometerReading[1]
this.accelerometerReading[2] = accelerometerReading[2]
computeOrientation()
}
magnetometerSensor.setOnSensorValuesChangedListener { magnetometerReading ->
this.magnetometerReading[0] = magnetometerReading[0]
this.magnetometerReading[1] = magnetometerReading[1]
this.magnetometerReading[2] = magnetometerReading[2]
}
}
private fun computeOrientation() {
SensorManager.getRotationMatrix(
rotationMatrix,
null,
accelerometerReading,
magnetometerReading
)
// "rotationMatrix" now has up-to-date information.
SensorManager.getOrientation(rotationMatrix, orientationAngles)
// "orientationAngles" now has up-to-date information.
val (_ , _ , y) = orientationAngles
// Values used here , are the ones needed by me , you may or may not need the same values
val orientationState = when (y.radToDegrees()) {
in 226..314 -> OrientationState.PERPENDICULAR
in 315..359, in 0..44 -> OrientationState.UPWARDS
in 136..225 -> OrientationState.DOWNWARDS
else -> OrientationState.PERPENDICULAR
}
viewModelScope.launch {
_orientation.emit(orientationState)
}
}
extension function to convert rad into degrees
fun Float.radToDegrees() : Int {
return (Math.toDegrees(this.toDouble()).toInt() + 360) % 360
}
Phillip Lackner Video can be used to learn sensor management in detail and computing the device orientation can be learned further more in android docs