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