1 package org.kde.kstars; 2 3 import org.qtproject.qt5.android.bindings.QtActivity; 4 import android.app.Activity; 5 import android.os.Bundle; 6 import android.view.Surface; 7 import android.view.Window; 8 import android.view.WindowManager; 9 import android.os.Build; 10 import android.content.Context; 11 import android.view.Display; 12 13 import android.hardware.Sensor; 14 import android.hardware.SensorManager; 15 16 import org.kde.kstars.math.Matrix4; 17 18 import org.kde.kstars.rotation.MagAccelListener; 19 import org.kde.kstars.rotation.RotationUpdateDelegate; 20 import org.kde.kstars.rotation.RotationVectorListener; 21 22 import org.kde.kstars.math.Matrix4; 23 import org.kde.kstars.math.Util; 24 import org.kde.kstars.math.Vector3; 25 import org.kde.kstars.math.Vector4; 26 27 import java.lang.Math; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 //import org.kde.kstars.OrientationCalculator; 33 34 public class DeviceOrientation extends QtActivity implements RotationUpdateDelegate 35 { 36 private SensorManager mSensorManager; 37 private int mDisplayRotation; 38 private Matrix4 mRotationMatrix = new Matrix4(); 39 private MagAccelListener mMagAccel; 40 private RotationVectorListener mRotationVector; 41 private boolean mUseRotationVector = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD); 42 private float[] m_orientation = new float[4]; 43 44 public float Pitch = 0.f; 45 public float Azimuth = 0.f; 46 public float Roll = 0.f; 47 getAzimuth()48 public float getAzimuth() { 49 return Azimuth; 50 } 51 getPitch()52 public float getPitch() { 53 return Pitch; 54 } 55 getRoll()56 public float getRoll() { 57 return Roll; 58 } 59 60 @Override onCreate(Bundle savedInstanceState)61 public void onCreate(Bundle savedInstanceState) { 62 super.onCreate(savedInstanceState); 63 64 // setup window decorations 65 // getWindow().requestFeature(Window.FEATURE_NO_TITLE); 66 final Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 67 mDisplayRotation = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) ? display.getRotation() : display.getOrientation(); 68 69 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 70 getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 71 } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { 72 73 } 74 75 // sensor listeners 76 mMagAccel = new MagAccelListener(this); 77 mRotationVector = new RotationVectorListener(this); 78 mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 79 80 mOrthographicProjectionMatrix.setToOrtho2D(0, 0, 1, -1); 81 82 for (int i = 0; i < NUM_POINTS; i++) { 83 mVertices.add(new Vector3()); 84 } 85 mOrthographicVertexBatch.addAll(mVertices); 86 mOrthographicVertexBatch.add(mSphereTop); 87 mOrthographicVertexBatch.add(mSphereBottom); 88 mOrthographicVertexBatch.add(mNorthReference); 89 } 90 91 // RotationUpdateDelegate methods 92 @Override onRotationUpdate(float[] newMatrix)93 public void onRotationUpdate(float[] newMatrix) { 94 // remap matrix values according to display rotation, as in 95 // SensorManager documentation. 96 final Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 97 mDisplayRotation = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) ? display.getRotation() : display.getOrientation(); 98 99 switch (mDisplayRotation) { 100 case Surface.ROTATION_0: 101 case Surface.ROTATION_180: 102 break; 103 case Surface.ROTATION_90: 104 SensorManager.remapCoordinateSystem(newMatrix, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, newMatrix); 105 break; 106 case Surface.ROTATION_270: 107 SensorManager.remapCoordinateSystem(newMatrix, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, newMatrix); 108 break; 109 default: 110 break; 111 } 112 mRotationMatrix.set(newMatrix); 113 getOrientation(mRotationMatrix, mDisplayRotation, m_orientation); 114 Azimuth = m_orientation[0]; 115 Pitch = m_orientation[1]; 116 Roll = m_orientation[2]; 117 } 118 119 // Other Activity life-cycle methods 120 @Override onPause()121 protected void onPause() { 122 super.onPause(); 123 mSensorManager.unregisterListener(mMagAccel); 124 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 125 mSensorManager.unregisterListener(mRotationVector); 126 } 127 } 128 applySensors(boolean useRV)129 private void applySensors(boolean useRV) { 130 mSensorManager.unregisterListener(mMagAccel); 131 mSensorManager.unregisterListener(mRotationVector); 132 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && useRV) { 133 mSensorManager.registerListener(mRotationVector, mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR), SensorManager.SENSOR_DELAY_GAME); 134 } else { 135 mSensorManager.registerListener(mMagAccel, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); 136 mSensorManager.registerListener(mMagAccel, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME); 137 } 138 } 139 140 @Override onResume()141 protected void onResume() { 142 super.onResume(); 143 applySensors(mUseRotationVector); 144 } 145 146 //For use from C++ startSensors()147 public void startSensors() { 148 applySensors(mUseRotationVector); 149 } 150 stopSensors()151 public void stopSensors() { 152 mSensorManager.unregisterListener(mMagAccel); 153 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 154 mSensorManager.unregisterListener(mRotationVector); 155 } 156 } 157 158 //Orientation calculation 159 private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f); 160 private static final float RADIANS_TO_DEGREES = (float) (180.0f / Math.PI); 161 162 // determines the number of points of the sphere 163 private static final int POINTS_PER_SEGMENT = 72; 164 private static final int NUM_SEGMENTS = 11; 165 private static final int NUM_POINTS = POINTS_PER_SEGMENT * NUM_SEGMENTS; 166 private static final int ORTHO_RESOLUTION = 1000; 167 168 private ArrayList<Vector3> mVertices = new ArrayList<Vector3>(NUM_POINTS + 1); 169 170 private Vector3 mRollTopAbsolute = new Vector3(), mRollBottomAbsolute = new Vector3(), mOriginPoint = new Vector3(), mReticlePoint = new Vector3(); 171 private Vector3 mSphereTop = new Vector3(), mSphereBottom = new Vector3(), mNorthReference = new Vector3(); 172 private Vector3 mNorthAbsolute = new Vector3(), mSouthAbsolute = new Vector3(), mWestAbsolute = new Vector3(), mEastAbsolute = new Vector3(); 173 174 private Matrix4 mOrthographicProjectionMatrix = new Matrix4(); 175 private Matrix4 mModelViewMatrix = new Matrix4(); 176 177 private List<Vector3> mOrthographicVertexBatch = new ArrayList<Vector3>(); 178 private Vector4 vTemp = new Vector4(); 179 getOrientation(Matrix4 rotationMatrix, int screenRotation, float[] out)180 public void getOrientation(Matrix4 rotationMatrix, int screenRotation, float[] out) { 181 rotatePoints(rotationMatrix, screenRotation); 182 183 Vector3 neighborPoint; 184 float dist = 0; 185 float dist2 = 0; 186 float closestPointDist = Float.MAX_VALUE; 187 float distLR = 0; 188 float angle = 0; 189 float neighborDist = 0, distToR = 0, distToL = 0; 190 float xScale = ORTHO_RESOLUTION; 191 float yScale = ORTHO_RESOLUTION; 192 float distN, distS, distW, distE; 193 float deviceRoll, deviceAltitude, deviceBearing; 194 195 // ALITTUDE 196 // altitude - very simple, triangulate between top and bottom point 197 mReticlePoint.set(0.5f * xScale, 0.5f * yScale, -xScale); 198 mOriginPoint.set(0.5f * xScale, 0.5f * yScale, 0); 199 dist = Util.calcDistance(mReticlePoint, mSphereBottom); 200 dist2 = Util.calcDistance(mReticlePoint, mSphereTop); 201 distToL = Util.calcDistance(mOriginPoint, mSphereBottom); 202 distToR = Util.calcDistance(mOriginPoint, mReticlePoint); 203 distLR = Util.calcDistance(mSphereBottom, mReticlePoint); 204 float altitude = RADIANS_TO_DEGREES * Util.calcAngle(distLR, Util.calcRadius(distToL, distToR, distLR)) - 90; 205 206 // flip quadrant if closer to top of globe 207 if (dist > dist2) { 208 altitude = -altitude; 209 } 210 211 deviceAltitude = altitude; 212 if (Float.isNaN(deviceAltitude)) { 213 deviceAltitude = 0; 214 } 215 216 // BEARING - if held flat, we calculate one way, if not, we calculate 217 // another 218 mReticlePoint.set(0.5f * xScale, 0.5f * yScale, -xScale); 219 if (Math.abs(deviceAltitude) < 75) { 220 int closestPoint = 0, pot1 = 0, pot2 = 0, neighbor = 0, left = 0; 221 int pointSize = NUM_POINTS; 222 // find closest point 223 for (int i = 0; i < pointSize; i++) { 224 Vector3 v = mVertices.get(i); 225 if (v.z < 0) { 226 dist = Util.calcDistance(mReticlePoint, v); 227 if (dist < closestPointDist) { 228 closestPointDist = dist; 229 closestPoint = i; 230 } 231 } 232 } 233 234 // potential neighbors for azimuth 235 if (closestPoint % POINTS_PER_SEGMENT == 0) { 236 pot1 = closestPoint + 1; 237 pot2 = closestPoint + POINTS_PER_SEGMENT - 1; 238 } else if ((closestPoint + 1) % POINTS_PER_SEGMENT == 0) { 239 pot1 = closestPoint - 1; 240 pot2 = closestPoint - POINTS_PER_SEGMENT - 1; 241 } else { 242 pot1 = closestPoint + 1; 243 pot2 = closestPoint - 1; 244 } 245 246 // bounds check 247 if (pot1 >= 0 && pot2 >= 0 && pot1 < pointSize && pot2 < pointSize) { 248 dist = Util.calcDistance(mReticlePoint, mVertices.get(pot1)); 249 dist2 = Util.calcDistance(mReticlePoint, mVertices.get(pot2)); 250 251 if (dist < dist2) { 252 neighbor = pot1; 253 neighborDist = dist; 254 } else { 255 neighbor = pot2; 256 neighborDist = dist2; 257 } 258 // boundary cases: 259 // closest is 345, right is 0 260 // closest is 0, left is 345 261 if (neighbor < closestPoint) { 262 // if we're 345, and point to right is 0, left should be 263 // 345, 264 // not 0 265 if ((closestPoint + 1) % POINTS_PER_SEGMENT == 0 && neighbor == pot2) { 266 left = closestPoint; 267 distToL = closestPointDist; 268 distToR = neighborDist; 269 } else { 270 left = neighbor; 271 distToL = neighborDist; 272 distToR = closestPointDist; 273 } 274 } else { 275 if (closestPoint % POINTS_PER_SEGMENT == 0 && neighbor == pot2) { 276 left = neighbor; 277 distToL = neighborDist; 278 distToR = closestPointDist; 279 } else { 280 left = closestPoint; 281 distToL = closestPointDist; 282 distToR = neighborDist; 283 } 284 } 285 } 286 287 if (neighbor <= NUM_POINTS - 1 && neighbor >= 0) { 288 neighborPoint = mVertices.get(neighbor); 289 } else { 290 if (neighbor < 0) { 291 neighborPoint = mSphereBottom; 292 } else { 293 neighborPoint = mSphereTop; 294 } 295 } 296 297 float angleIncrement = (360.0f / POINTS_PER_SEGMENT); 298 angle = left % POINTS_PER_SEGMENT * angleIncrement; 299 distLR = Util.calcDistance(mVertices.get(closestPoint), neighborPoint); 300 301 deviceBearing = Util.floatrev(angle + angleIncrement * ((Math.cos(Util.calcAngleClamp(distToR, Util.calcRadius(distToL, distToR, distLR))) * distToL) / distLR) - 180); 302 } else { 303 // calc current N Point distance from original compass points 304 mNorthAbsolute.set(0.5f * xScale, 0.2f * xScale + (yScale - xScale) / 2, 0); 305 mSouthAbsolute.set(0.5f * xScale, 0.8f * xScale + (yScale - xScale) / 2, 0); 306 mWestAbsolute.set(0.2f * xScale, 0.5f * yScale, 0); 307 mEastAbsolute.set(0.8f * xScale, 0.5f * yScale, 0); 308 309 distN = Util.calcDistance(mNorthReference, mNorthAbsolute); 310 distS = Util.calcDistance(mNorthReference, mSouthAbsolute); 311 distW = Util.calcDistance(mNorthReference, mWestAbsolute); 312 distE = Util.calcDistance(mNorthReference, mEastAbsolute); 313 314 distToL = Util.calcDistance(mOriginPoint, mNorthReference); 315 distToR = Util.calcDistance(mOriginPoint, mNorthAbsolute); 316 distLR = Util.calcDistance(mNorthReference, mNorthAbsolute); 317 318 float bearing = RADIANS_TO_DEGREES * -Util.calcAngle(distLR, Util.calcRadius(distToL, distToR, distLR)); 319 320 if (distN < distS) { 321 if (distW < distE) { 322 bearing = 360 - bearing; 323 } 324 } else { 325 if (distW < distE) { 326 bearing += 180; 327 } else { 328 bearing = 180 - bearing; 329 } 330 } 331 332 if (deviceAltitude > 0) { 333 bearing = 180 - bearing; 334 } 335 deviceBearing = Util.floatrev(bearing); 336 } 337 338 if (Float.isNaN(deviceBearing)) { 339 deviceBearing = 0; 340 } 341 342 // ROLL - calculate only when not held flat, ignore when altitude 343 // (pitch) is less than 15 344 if (Math.abs(deviceAltitude) < 75) { 345 mRollTopAbsolute.set(0.5f * xScale, 0.2f * yScale, 0); 346 mRollBottomAbsolute.set(0.5f * xScale, 0.8f * yScale, 0); 347 348 Vector3 upDown; 349 Vector3 topBot; 350 boolean altAbove = true; 351 352 if (deviceAltitude < 0) { 353 altAbove = false; 354 upDown = mRollBottomAbsolute; 355 topBot = mSphereTop; 356 } else { 357 upDown = mRollTopAbsolute; 358 topBot = mSphereBottom; 359 } 360 361 float adist = (float) Math.sqrt((upDown.x - mOriginPoint.x) * (upDown.x - mOriginPoint.x) + (upDown.y - mOriginPoint.y) * (upDown.y - mOriginPoint.y)); 362 float bdist = (float) Math.sqrt((upDown.x - topBot.x) * (upDown.x - topBot.x) + (upDown.y - topBot.y) * (upDown.y - topBot.y)); 363 float cdist = (float) Math.sqrt((mOriginPoint.x - topBot.x) * (mOriginPoint.x - topBot.x) + (mOriginPoint.y - topBot.y) * (mOriginPoint.y - topBot.y)); 364 365 float val = ((adist * adist) + (cdist * cdist) - (bdist * bdist)) / (2 * adist * cdist); 366 if (val < 1) { 367 float atheta = (float) Math.acos(((adist * adist) + (cdist * cdist) - (bdist * bdist)) / (2 * adist * cdist)) * RADIANS_TO_DEGREES; 368 if (altAbove) { 369 if (upDown.x - topBot.x < 0) { 370 atheta = -atheta; 371 } 372 } else { 373 if (upDown.x - topBot.x > 0) { 374 atheta = -atheta; 375 } 376 } 377 atheta = Util.floatrev(atheta); 378 // restrict the roll to increments of 0.5 degrees - we don't 379 // need the precision here, also steady within 3 degrees 380 if (atheta <= 3 || atheta >= 357) { 381 atheta = 0; 382 } else { 383 atheta = 0.5f * Math.round(atheta / 0.5); 384 } 385 deviceRoll = atheta; 386 } else { 387 deviceRoll = 0; 388 } 389 } else { 390 deviceRoll = 0; 391 } 392 if (Float.isNaN(deviceRoll)) { 393 deviceRoll = 0; 394 } 395 out[0] = deviceBearing; 396 out[1] = deviceAltitude; 397 out[2] = deviceRoll; 398 } 399 resetPoints()400 private void resetPoints() { 401 mSphereTop.set(0, 0, 1); 402 mSphereBottom.set(0, 0, -1); 403 404 for (int j = 0; j < NUM_SEGMENTS; j++) { 405 int idx = j - 5; 406 float jCosVal = (float) Math.cos(DEGREES_TO_RADIANS * (float) (idx * 15)); 407 float jCosValInv = (float) Math.cos(DEGREES_TO_RADIANS * (float) (90 - idx * 15)); 408 for (int i = 0; i < POINTS_PER_SEGMENT; i++) { 409 float sinVal = (float) Math.sin(DEGREES_TO_RADIANS * (float) (i * (360 / POINTS_PER_SEGMENT))); 410 float cosVal = (float) Math.cos(DEGREES_TO_RADIANS * (float) (i * (360 / POINTS_PER_SEGMENT))); 411 mVertices.get(i + (POINTS_PER_SEGMENT * j)).set(sinVal * jCosVal * 1, -cosVal * jCosVal * 1, jCosValInv * 1); 412 } 413 } 414 415 // a reference to the N point on the sphere. 416 mNorthReference.set(mVertices.get(POINTS_PER_SEGMENT * 5)); 417 } 418 419 /** 420 * With rotationMatrix, rotate the view components 421 * 422 * @param rotationMatrix 423 */ rotatePoints(Matrix4 rotationMatrix, int screenRotation)424 public void rotatePoints(Matrix4 rotationMatrix, int screenRotation) { 425 resetPoints(); 426 final int width = ORTHO_RESOLUTION; 427 final int height = ORTHO_RESOLUTION; 428 final float orthoScale = 1.0f; 429 mModelViewMatrix.idt().mul(mOrthographicProjectionMatrix).mul(rotationMatrix); 430 switch (screenRotation) { 431 case Surface.ROTATION_0: 432 case Surface.ROTATION_90: 433 case Surface.ROTATION_270: 434 for (Vector3 v : mOrthographicVertexBatch) { 435 vTemp.set(v.x, -v.y, -v.z, 0); 436 vTemp.mul(mModelViewMatrix); 437 v.x = (vTemp.x) * 0.5f * width * orthoScale + width / 2; 438 v.y = (vTemp.y) * 0.5f * width * orthoScale + width / 2 + (height - width) / 2; 439 v.z = vTemp.z * 0.5f * width * orthoScale; 440 } 441 break; 442 // For 180, we have to reflect x and y values 443 case Surface.ROTATION_180: 444 for (Vector3 v : mOrthographicVertexBatch) { 445 vTemp.set(v.x, -v.y, -v.z, 0); 446 vTemp.mul(mModelViewMatrix); 447 // reflect x and y axes... 448 v.x = (-vTemp.x) * 0.5f * width + width / 2; 449 v.y = (-vTemp.y) * 0.5f * width + width / 2 + (height - width) / 2; 450 v.z = vTemp.z * 0.5f * width; 451 } 452 break; 453 } 454 } 455 } 456