├── LICENSE ├── README.md └── RotationVectorCompass ├── AndroidManifest.xml ├── lint.xml ├── proguard.cfg ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── drawable │ └── translucent_rounded_rectangle.xml ├── layout │ └── main.xml └── values │ └── strings.xml └── src ├── .DS_Store └── com └── adamratana └── rotationvectorcompass ├── OrientationCalculator.java ├── OrientationCalculatorImpl.java ├── OverlayView.java ├── RotationVectorCompass.java ├── camera ├── CameraPreviewLayer.java └── CameraUtil.java ├── drawing ├── CompassComponent.java ├── DrawingComponent.java ├── LineBatcher.java └── ReticleComponent.java ├── math ├── MathUtils.java ├── Matrix3.java ├── Matrix4.java ├── NumberUtils.java ├── Quaternion.java ├── Util.java ├── Vector2.java ├── Vector3.java └── Vector4.java └── rotation ├── MagAccelListener.java ├── RotationUpdateDelegate.java └── RotationVectorListener.java /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Rotation Vector Augmented Reality Compass 2 | 3 | ###An Augmented Reality Compass using the Rotation Vector Sensor for Android: 4 | This project is a proof of concept for various experiments involving the Rotation Vector Sensor, Augmented Reality, overlaying these on top of a Camera Preview, and using the Canvas API for drawing. 5 | 6 | Contributions welcome, especially with regard to improving compatibility with any non-conforming devices, and with improving the orientation derivation. 7 | 8 | ## Uses the Canvas API for drawing: 9 | 10 | Why the canvas API? Because the canvas is easy to understand for most people with little to no graphics programming experience and to those with no knowledge of OpenGL. 11 | 12 | In addition we manually perform rotation and translation and vector manipulation, which can serve as an introduction to these concepts. 13 | 14 | ## Uses Orthographic and Perspective Projections: 15 | 16 | As an academic display of how both work, to compare and contrast, especially for those who many be unfamiliar. 17 | 18 | ## Open-source [LibGDX](https://github.com/libgdx/libgdx) (Apache 2.0) for certain math classes: 19 | 20 | Uses [LibGDX](https://github.com/libgdx/libgdx) for Matrix, Quaternion, Vector3 classes; Vector4 derived from Vector3 21 | 22 | ## Uses the Rotation Vector virtual Sensor: 23 | 24 | This has been little-documented, and the documentation might not make much sense to someone without 3d graphics or OpenGL experience. We show it in use and in contrast to the typical sensor fusion of Magnetometer / Accelerometer. 25 | 26 | Some devices may not properly implement this, the galaxy tab 10.1 (version 1) does not seem to have a consistent implementation. 27 | 28 | ## Has a basic, filtered Sensor Fusion implementation: 29 | 30 | Uses the Magnetometer / Accelerometer to detect device rotation, with a simple filter to smooth the values. 31 | 32 | ## Aims to provide an accurate representation of device orientation in 3d with respect to North: 33 | 34 | A brute-force approach, but using typical API methods such as SensorManager.getOrientation() do not provide acceptable values for all positions 35 | 36 | ## Camera preview integration, with portrait mode support: 37 | 38 | - Overlays the compass on top of the camera preview 39 | - Uses the camera reported field of view information to properly apply perspective to the compass 40 | - Supports the Camera when the devices is held in Portrait mode, could find few working examples of this 41 | 42 | ## Employs fixes for known device abberations in the wild: 43 | 44 | - Some devices report 0 for field of view 45 | - ZTE Blade crashes while calling Camera.Parameters.getHorizontalViewAngle() 46 | - Some devices when rotating the camera display by 0 degrees do not behave correctly 47 | 48 | ## Supports Variable Device Orientation, Full Screen: 49 | 50 | - Allows the rotation of the device, rather than locking to portrait or landscape 51 | - Programatically allows for locking of the current orientation 52 | - Programatically allows for toggling of full screen display 53 | 54 | ## Conditional support back to 2.1: 55 | 56 | Branching is applied for API calls which are incompatible with 2.1, 2.2: 57 | 58 | - Setting the camera display rotation is not possible in 2.1 59 | - Getting the camera field of view is also not possible in 2.1 60 | - Rotation Vector not available prior to 2.3 61 | -------------------------------------------------------------------------------- /RotationVectorCompass/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /RotationVectorCompass/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /RotationVectorCompass/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembers class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers enum * { 30 | public static **[] values(); 31 | public static ** valueOf(java.lang.String); 32 | } 33 | 34 | -keep class * implements android.os.Parcelable { 35 | public static final android.os.Parcelable$Creator *; 36 | } 37 | -------------------------------------------------------------------------------- /RotationVectorCompass/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-14 12 | -------------------------------------------------------------------------------- /RotationVectorCompass/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratana/rotation-vector-compass/7a64bed81a8012867b4dfc4a9d7c88cd4a36e927/RotationVectorCompass/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /RotationVectorCompass/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratana/rotation-vector-compass/7a64bed81a8012867b4dfc4a9d7c88cd4a36e927/RotationVectorCompass/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /RotationVectorCompass/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratana/rotation-vector-compass/7a64bed81a8012867b4dfc4a9d7c88cd4a36e927/RotationVectorCompass/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /RotationVectorCompass/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratana/rotation-vector-compass/7a64bed81a8012867b4dfc4a9d7c88cd4a36e927/RotationVectorCompass/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /RotationVectorCompass/res/drawable/translucent_rounded_rectangle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /RotationVectorCompass/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 32 | 33 | 46 | 47 | 60 | 61 | 74 | 75 | 88 | 89 | 103 | 104 | 117 | -------------------------------------------------------------------------------- /RotationVectorCompass/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rotation Vector Compass 4 | 1.0 5 | 6 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ratana/rotation-vector-compass/7a64bed81a8012867b4dfc4a9d7c88cd4a36e927/RotationVectorCompass/src/.DS_Store -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/OrientationCalculator.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass; 2 | 3 | import com.adamratana.rotationvectorcompass.math.Matrix4; 4 | 5 | public interface OrientationCalculator { 6 | /** 7 | * Given a rotation matrix and the current device screen rotation, produce 8 | * values for azimuth, altitude, roll (yaw, pitch, roll) 9 | * 10 | * @param rotationMatrix 11 | * - device rotation 12 | * @param screenRotation 13 | * - device screen rotation 14 | * @param out 15 | * - array of float[3] to dump values into 16 | */ 17 | public void getOrientation(Matrix4 rotationMatrix, int screenRotation, float[] out); 18 | } 19 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/OrientationCalculatorImpl.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import android.util.FloatMath; 7 | import android.view.Surface; 8 | 9 | import com.adamratana.rotationvectorcompass.math.Matrix4; 10 | import com.adamratana.rotationvectorcompass.math.Util; 11 | import com.adamratana.rotationvectorcompass.math.Vector3; 12 | import com.adamratana.rotationvectorcompass.math.Vector4; 13 | 14 | /** 15 | * A really brute-force implementation to derive the orientation of the device 16 | * based on where the back of the device is pointing when not held flat, or, 17 | * when held flat, where the top of the device is pointing. This is tricky, 18 | * because where the device is pointing changes depending on how we're holding 19 | * the device, as well as the screen rotation of the device. 20 | * 21 | * What this does is rotate all the points according to the rotation matrix, and 22 | * then take a look at where reference points ended up (6 points on a sphere, to 23 | * determine bearing, pitch, roll). Yeah, it's bad, and there has to be a much 24 | * simpler way. 25 | * 26 | * TODO: replace with something that works far more efficiently and reasonably. 27 | * 28 | * @author Adam 29 | * 30 | */ 31 | public class OrientationCalculatorImpl implements OrientationCalculator { 32 | private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f); 33 | private static final float RADIANS_TO_DEGREES = (float) (180.0f / Math.PI); 34 | 35 | // determines the number of points of the sphere 36 | private static final int POINTS_PER_SEGMENT = 72; 37 | private static final int NUM_SEGMENTS = 11; 38 | private static final int NUM_POINTS = POINTS_PER_SEGMENT * NUM_SEGMENTS; 39 | private static final int ORTHO_RESOLUTION = 1000; 40 | 41 | private ArrayList mVertices = new ArrayList(NUM_POINTS + 1); 42 | 43 | private Vector3 mRollTopAbsolute = new Vector3(), mRollBottomAbsolute = new Vector3(), mOriginPoint = new Vector3(), mReticlePoint = new Vector3(); 44 | private Vector3 mSphereTop = new Vector3(), mSphereBottom = new Vector3(), mNorthReference = new Vector3(); 45 | private Vector3 mNorthAbsolute = new Vector3(), mSouthAbsolute = new Vector3(), mWestAbsolute = new Vector3(), mEastAbsolute = new Vector3(); 46 | 47 | private Matrix4 mOrthographicProjectionMatrix = new Matrix4(); 48 | private Matrix4 mModelViewMatrix = new Matrix4(); 49 | 50 | private List mOrthographicVertexBatch = new ArrayList(); 51 | private Vector4 vTemp = new Vector4(); 52 | 53 | public OrientationCalculatorImpl() { 54 | mOrthographicProjectionMatrix.setToOrtho2D(0, 0, 1, -1); 55 | 56 | for (int i = 0; i < NUM_POINTS; i++) { 57 | mVertices.add(new Vector3()); 58 | } 59 | mOrthographicVertexBatch.addAll(mVertices); 60 | mOrthographicVertexBatch.add(mSphereTop); 61 | mOrthographicVertexBatch.add(mSphereBottom); 62 | mOrthographicVertexBatch.add(mNorthReference); 63 | } 64 | 65 | @Override 66 | public void getOrientation(Matrix4 rotationMatrix, int screenRotation, float[] out) { 67 | rotatePoints(rotationMatrix, screenRotation); 68 | 69 | Vector3 neighborPoint; 70 | float dist = 0; 71 | float dist2 = 0; 72 | float closestPointDist = Float.MAX_VALUE; 73 | float distLR = 0; 74 | float angle = 0; 75 | float neighborDist = 0, distToR = 0, distToL = 0; 76 | float xScale = ORTHO_RESOLUTION; 77 | float yScale = ORTHO_RESOLUTION; 78 | float distN, distS, distW, distE; 79 | float deviceRoll, deviceAltitude, deviceBearing; 80 | 81 | // ALITTUDE 82 | // altitude - very simple, triangulate between top and bottom point 83 | mReticlePoint.set(0.5f * xScale, 0.5f * yScale, -xScale); 84 | mOriginPoint.set(0.5f * xScale, 0.5f * yScale, 0); 85 | dist = Util.calcDistance(mReticlePoint, mSphereBottom); 86 | dist2 = Util.calcDistance(mReticlePoint, mSphereTop); 87 | distToL = Util.calcDistance(mOriginPoint, mSphereBottom); 88 | distToR = Util.calcDistance(mOriginPoint, mReticlePoint); 89 | distLR = Util.calcDistance(mSphereBottom, mReticlePoint); 90 | float altitude = RADIANS_TO_DEGREES * Util.calcAngle(distLR, Util.calcRadius(distToL, distToR, distLR)) - 90; 91 | 92 | // flip quadrant if closer to top of globe 93 | if (dist > dist2) { 94 | altitude = -altitude; 95 | } 96 | 97 | deviceAltitude = altitude; 98 | if (Float.isNaN(deviceAltitude)) { 99 | deviceAltitude = 0; 100 | } 101 | 102 | // BEARING - if held flat, we calculate one way, if not, we calculate 103 | // another 104 | mReticlePoint.set(0.5f * xScale, 0.5f * yScale, -xScale); 105 | if (Math.abs(deviceAltitude) < 75) { 106 | int closestPoint = 0, pot1 = 0, pot2 = 0, neighbor = 0, left = 0; 107 | int pointSize = NUM_POINTS; 108 | // find closest point 109 | for (int i = 0; i < pointSize; i++) { 110 | Vector3 v = mVertices.get(i); 111 | if (v.z < 0) { 112 | dist = Util.calcDistance(mReticlePoint, v); 113 | if (dist < closestPointDist) { 114 | closestPointDist = dist; 115 | closestPoint = i; 116 | } 117 | } 118 | } 119 | 120 | // potential neighbors for azimuth 121 | if (closestPoint % POINTS_PER_SEGMENT == 0) { 122 | pot1 = closestPoint + 1; 123 | pot2 = closestPoint + POINTS_PER_SEGMENT - 1; 124 | } else if ((closestPoint + 1) % POINTS_PER_SEGMENT == 0) { 125 | pot1 = closestPoint - 1; 126 | pot2 = closestPoint - POINTS_PER_SEGMENT - 1; 127 | } else { 128 | pot1 = closestPoint + 1; 129 | pot2 = closestPoint - 1; 130 | } 131 | 132 | // bounds check 133 | if (pot1 >= 0 && pot2 >= 0 && pot1 < pointSize && pot2 < pointSize) { 134 | dist = Util.calcDistance(mReticlePoint, mVertices.get(pot1)); 135 | dist2 = Util.calcDistance(mReticlePoint, mVertices.get(pot2)); 136 | 137 | if (dist < dist2) { 138 | neighbor = pot1; 139 | neighborDist = dist; 140 | } else { 141 | neighbor = pot2; 142 | neighborDist = dist2; 143 | } 144 | // boundary cases: 145 | // closest is 345, right is 0 146 | // closest is 0, left is 345 147 | if (neighbor < closestPoint) { 148 | // if we're 345, and point to right is 0, left should be 149 | // 345, 150 | // not 0 151 | if ((closestPoint + 1) % POINTS_PER_SEGMENT == 0 && neighbor == pot2) { 152 | left = closestPoint; 153 | distToL = closestPointDist; 154 | distToR = neighborDist; 155 | } else { 156 | left = neighbor; 157 | distToL = neighborDist; 158 | distToR = closestPointDist; 159 | } 160 | } else { 161 | if (closestPoint % POINTS_PER_SEGMENT == 0 && neighbor == pot2) { 162 | left = neighbor; 163 | distToL = neighborDist; 164 | distToR = closestPointDist; 165 | } else { 166 | left = closestPoint; 167 | distToL = closestPointDist; 168 | distToR = neighborDist; 169 | } 170 | } 171 | } 172 | 173 | if (neighbor <= NUM_POINTS - 1 && neighbor >= 0) { 174 | neighborPoint = mVertices.get(neighbor); 175 | } else { 176 | if (neighbor < 0) { 177 | neighborPoint = mSphereBottom; 178 | } else { 179 | neighborPoint = mSphereTop; 180 | } 181 | } 182 | 183 | float angleIncrement = (360.0f / POINTS_PER_SEGMENT); 184 | angle = left % POINTS_PER_SEGMENT * angleIncrement; 185 | distLR = Util.calcDistance(mVertices.get(closestPoint), neighborPoint); 186 | 187 | deviceBearing = Util.floatrev(angle + angleIncrement * ((FloatMath.cos(Util.calcAngleClamp(distToR, Util.calcRadius(distToL, distToR, distLR))) * distToL) / distLR) - 180); 188 | } else { 189 | // calc current N Point distance from original compass points 190 | mNorthAbsolute.set(0.5f * xScale, 0.2f * xScale + (yScale - xScale) / 2, 0); 191 | mSouthAbsolute.set(0.5f * xScale, 0.8f * xScale + (yScale - xScale) / 2, 0); 192 | mWestAbsolute.set(0.2f * xScale, 0.5f * yScale, 0); 193 | mEastAbsolute.set(0.8f * xScale, 0.5f * yScale, 0); 194 | 195 | distN = Util.calcDistance(mNorthReference, mNorthAbsolute); 196 | distS = Util.calcDistance(mNorthReference, mSouthAbsolute); 197 | distW = Util.calcDistance(mNorthReference, mWestAbsolute); 198 | distE = Util.calcDistance(mNorthReference, mEastAbsolute); 199 | 200 | distToL = Util.calcDistance(mOriginPoint, mNorthReference); 201 | distToR = Util.calcDistance(mOriginPoint, mNorthAbsolute); 202 | distLR = Util.calcDistance(mNorthReference, mNorthAbsolute); 203 | 204 | float bearing = RADIANS_TO_DEGREES * -Util.calcAngle(distLR, Util.calcRadius(distToL, distToR, distLR)); 205 | 206 | if (distN < distS) { 207 | if (distW < distE) { 208 | bearing = 360 - bearing; 209 | } 210 | } else { 211 | if (distW < distE) { 212 | bearing += 180; 213 | } else { 214 | bearing = 180 - bearing; 215 | } 216 | } 217 | 218 | if (deviceAltitude > 0) { 219 | bearing = 180 - bearing; 220 | } 221 | deviceBearing = Util.floatrev(bearing); 222 | } 223 | 224 | if (Float.isNaN(deviceBearing)) { 225 | deviceBearing = 0; 226 | } 227 | 228 | // ROLL - calculate only when not held flat, ignore when altitude 229 | // (pitch) is less than 15 230 | if (Math.abs(deviceAltitude) < 75) { 231 | mRollTopAbsolute.set(0.5f * xScale, 0.2f * yScale, 0); 232 | mRollBottomAbsolute.set(0.5f * xScale, 0.8f * yScale, 0); 233 | 234 | Vector3 upDown; 235 | Vector3 topBot; 236 | boolean altAbove = true; 237 | 238 | if (deviceAltitude < 0) { 239 | altAbove = false; 240 | upDown = mRollBottomAbsolute; 241 | topBot = mSphereTop; 242 | } else { 243 | upDown = mRollTopAbsolute; 244 | topBot = mSphereBottom; 245 | } 246 | 247 | float adist = FloatMath.sqrt((upDown.x - mOriginPoint.x) * (upDown.x - mOriginPoint.x) + (upDown.y - mOriginPoint.y) * (upDown.y - mOriginPoint.y)); 248 | float bdist = FloatMath.sqrt((upDown.x - topBot.x) * (upDown.x - topBot.x) + (upDown.y - topBot.y) * (upDown.y - topBot.y)); 249 | float cdist = FloatMath.sqrt((mOriginPoint.x - topBot.x) * (mOriginPoint.x - topBot.x) + (mOriginPoint.y - topBot.y) * (mOriginPoint.y - topBot.y)); 250 | 251 | float val = ((adist * adist) + (cdist * cdist) - (bdist * bdist)) / (2 * adist * cdist); 252 | if (val < 1) { 253 | float atheta = (float) Math.acos(((adist * adist) + (cdist * cdist) - (bdist * bdist)) / (2 * adist * cdist)) * RADIANS_TO_DEGREES; 254 | if (altAbove) { 255 | if (upDown.x - topBot.x < 0) { 256 | atheta = -atheta; 257 | } 258 | } else { 259 | if (upDown.x - topBot.x > 0) { 260 | atheta = -atheta; 261 | } 262 | } 263 | atheta = Util.floatrev(atheta); 264 | // restrict the roll to increments of 0.5 degrees - we don't 265 | // need the precision here, also steady within 3 degrees 266 | if (atheta <= 3 || atheta >= 357) { 267 | atheta = 0; 268 | } else { 269 | atheta = 0.5f * Math.round(atheta / 0.5); 270 | } 271 | deviceRoll = atheta; 272 | } else { 273 | deviceRoll = 0; 274 | } 275 | } else { 276 | deviceRoll = 0; 277 | } 278 | if (Float.isNaN(deviceRoll)) { 279 | deviceRoll = 0; 280 | } 281 | out[0] = deviceBearing; 282 | out[1] = deviceAltitude; 283 | out[2] = deviceRoll; 284 | } 285 | 286 | private void resetPoints() { 287 | mSphereTop.set(0, 0, 1); 288 | mSphereBottom.set(0, 0, -1); 289 | 290 | for (int j = 0; j < NUM_SEGMENTS; j++) { 291 | int idx = j - 5; 292 | float jCosVal = FloatMath.cos(DEGREES_TO_RADIANS * (float) (idx * 15)); 293 | float jCosValInv = FloatMath.cos(DEGREES_TO_RADIANS * (float) (90 - idx * 15)); 294 | for (int i = 0; i < POINTS_PER_SEGMENT; i++) { 295 | float sinVal = FloatMath.sin(DEGREES_TO_RADIANS * (float) (i * (360 / POINTS_PER_SEGMENT))); 296 | float cosVal = FloatMath.cos(DEGREES_TO_RADIANS * (float) (i * (360 / POINTS_PER_SEGMENT))); 297 | mVertices.get(i + (POINTS_PER_SEGMENT * j)).set(sinVal * jCosVal * 1, -cosVal * jCosVal * 1, jCosValInv * 1); 298 | } 299 | } 300 | 301 | // a reference to the N point on the sphere. 302 | mNorthReference.set(mVertices.get(POINTS_PER_SEGMENT * 5)); 303 | } 304 | 305 | /** 306 | * With rotationMatrix, rotate the view components 307 | * 308 | * @param rotationMatrix 309 | */ 310 | public void rotatePoints(Matrix4 rotationMatrix, int screenRotation) { 311 | resetPoints(); 312 | final int width = ORTHO_RESOLUTION; 313 | final int height = ORTHO_RESOLUTION; 314 | final float orthoScale = 1.0f; 315 | mModelViewMatrix.idt().mul(mOrthographicProjectionMatrix).mul(rotationMatrix); 316 | switch (screenRotation) { 317 | case Surface.ROTATION_0: 318 | case Surface.ROTATION_90: 319 | case Surface.ROTATION_270: 320 | for (Vector3 v : mOrthographicVertexBatch) { 321 | vTemp.set(v.x, -v.y, -v.z, 0); 322 | vTemp.mul(mModelViewMatrix); 323 | v.x = (vTemp.x) * 0.5f * width * orthoScale + width / 2; 324 | v.y = (vTemp.y) * 0.5f * width * orthoScale + width / 2 + (height - width) / 2; 325 | v.z = vTemp.z * 0.5f * width * orthoScale; 326 | } 327 | break; 328 | // For 180, we have to reflect x and y values 329 | case Surface.ROTATION_180: 330 | for (Vector3 v : mOrthographicVertexBatch) { 331 | vTemp.set(v.x, -v.y, -v.z, 0); 332 | vTemp.mul(mModelViewMatrix); 333 | // reflect x and y axes... 334 | v.x = (-vTemp.x) * 0.5f * width + width / 2; 335 | v.y = (-vTemp.y) * 0.5f * width + width / 2 + (height - width) / 2; 336 | v.z = vTemp.z * 0.5f * width; 337 | } 338 | break; 339 | } 340 | } 341 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/OverlayView.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass; 2 | 3 | import java.text.DecimalFormat; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import android.annotation.SuppressLint; 8 | import android.content.Context; 9 | import android.graphics.Canvas; 10 | import android.graphics.Paint; 11 | import android.os.Build; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.view.Display; 15 | import android.view.Surface; 16 | import android.view.View; 17 | import android.view.WindowManager; 18 | import android.widget.TextView; 19 | 20 | import com.adamratana.rotationvectorcompass.drawing.CompassComponent; 21 | import com.adamratana.rotationvectorcompass.drawing.DrawingComponent; 22 | import com.adamratana.rotationvectorcompass.drawing.ReticleComponent; 23 | import com.adamratana.rotationvectorcompass.math.Matrix4; 24 | import com.adamratana.rotationvectorcompass.math.Vector3; 25 | import com.adamratana.rotationvectorcompass.math.Vector4; 26 | 27 | public class OverlayView extends View { 28 | /** 29 | * for formatting orientation values, fov 30 | */ 31 | private static final DecimalFormat degreeFormat = new DecimalFormat("##0.0\u00B0"); 32 | 33 | /** 34 | * for formatting orthographic scale 35 | */ 36 | private static final DecimalFormat scaleFormat = new DecimalFormat("##0.0"); 37 | 38 | /** 39 | * for debugging, set to true; 40 | */ 41 | private static final boolean FLAG_DEBUG = true; 42 | 43 | /** 44 | * for logging, fps counter 45 | */ 46 | private static final String TAG = "OverlayView"; 47 | 48 | /** 49 | * Lock used to mitigate between objects being rotated on a secondary thread 50 | * (methods called by motion event listeners), and objects being displayed 51 | * on the screen. we draw based on published values, and we lock while 52 | * publishing those values, or drawing those published values, so values are 53 | * not updated in mid-draw. 54 | * 55 | */ 56 | private Object mPublishingLock = new Object(); 57 | 58 | /** 59 | * a matrix to use for creating a perspective projection transform 60 | */ 61 | private Matrix4 mPerspectiveProjectionMatrix = new Matrix4(); 62 | /** 63 | * Matrix to use for creating an orthographic projection transform 64 | */ 65 | private Matrix4 mOrthographicProjectionMatrix = new Matrix4(); 66 | /** 67 | * Matrix used to transform components, using perspective, rotation, etc. 68 | */ 69 | private Matrix4 mModelViewMatrix = new Matrix4(); 70 | 71 | /** 72 | * Used to derive the orientation of the device 73 | */ 74 | private OrientationCalculator mOrientationCalculator = new OrientationCalculatorImpl(); 75 | 76 | /** 77 | * device orientation - bearing, pitch, roll 78 | */ 79 | float[] mDerivedDeviceOrientation = { 0, 0, 0 }; 80 | 81 | /** 82 | * the radius is based on the lessor of view width and height, divided by 2 83 | */ 84 | private float mDrawRadius = 0; 85 | 86 | /** 87 | * the perspective projection's field of view 88 | */ 89 | private float mPerspectiveFOV = 60.0f; 90 | 91 | /** 92 | * orthorgraphic zoom scale 93 | */ 94 | private float mOrthographicScale = 1.0f; 95 | 96 | /** 97 | * aspect ratio of the view 98 | */ 99 | private float mViewAspectRatio; 100 | 101 | /** 102 | * batch of vertices to rotate/translate/scale for all components 103 | */ 104 | private ArrayList mPerspectiveVertexBatch = new ArrayList(); 105 | 106 | private ArrayList mOrthographicVertexBatch = new ArrayList(); 107 | 108 | /** 109 | * the 3d compass 110 | */ 111 | private CompassComponent mCompassComponent = new CompassComponent(); 112 | 113 | /** 114 | * the reticle 115 | */ 116 | private ReticleComponent mReticleComponent = new ReticleComponent(); 117 | 118 | /** 119 | * list of augmented reality items to display 120 | */ 121 | private List mDisplayList = new ArrayList(); 122 | 123 | /** 124 | * handle on the text view in which to output orientation information and 125 | * field of view information 126 | */ 127 | private TextView mFOVView; 128 | 129 | /** 130 | * how the display is rotated. will be Surface.ROTATION_0 to 131 | * Surface.ROTATION_270 132 | */ 133 | private int mDisplayRotation = 0; 134 | 135 | private Vector4 vTemp = new Vector4(); 136 | 137 | /** 138 | * used for drawing our items with the canvas API 139 | */ 140 | private Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); 141 | 142 | /** 143 | * sets the view in which to display the field of view and orientation 144 | * 145 | * @param fovView 146 | */ 147 | public void setFOVView(TextView fovView) { 148 | mFOVView = fovView; 149 | } 150 | 151 | /** 152 | * determines whether we are using a perspective projection or orthographic 153 | */ 154 | private boolean mPerspectiveProjection = true; 155 | 156 | /** 157 | * when true, uses perspective, when false, is orthographic 158 | * 159 | * @param usePerspectiveProjection 160 | */ 161 | public void setPerspectiveProjection(boolean usePerspectiveProjection) { 162 | mPerspectiveProjection = usePerspectiveProjection; 163 | } 164 | 165 | /** 166 | * set the scale for the orthographic projection 167 | * 168 | * @param degrees 169 | */ 170 | public void setOrthograhicScale(float scale) { 171 | mOrthographicScale = scale; 172 | updateOrientationDisplay(); 173 | } 174 | 175 | /** 176 | * set the FOV for the perspective projection 177 | * 178 | * @param degrees 179 | */ 180 | public void setFOV(double degrees) { 181 | mPerspectiveFOV = (float) degrees; 182 | updatePerspectiveProjectionMatrix(); 183 | updateOrientationDisplay(); 184 | } 185 | 186 | public OverlayView(Context context) { 187 | super(context); 188 | init(); 189 | } 190 | 191 | public OverlayView(Context context, AttributeSet attrs, int defStyle) { 192 | super(context, attrs, defStyle); 193 | init(); 194 | } 195 | 196 | public OverlayView(Context context, AttributeSet attrs) { 197 | super(context, attrs); 198 | init(); 199 | } 200 | 201 | /** 202 | * update the display text for fov and device orientation 203 | */ 204 | private void updateOrientationDisplay() { 205 | if (mPerspectiveProjection) { 206 | mFOVView.setText(degreeFormat.format(mDerivedDeviceOrientation[0]) + "\n" + degreeFormat.format(mDerivedDeviceOrientation[1]) + "\n" + degreeFormat.format(mDerivedDeviceOrientation[2]) + "\n\nv fov\n" + degreeFormat.format(mPerspectiveFOV)); 207 | } else { 208 | mFOVView.setText(degreeFormat.format(mDerivedDeviceOrientation[0]) + "\n" + degreeFormat.format(mDerivedDeviceOrientation[1]) + "\n" + degreeFormat.format(mDerivedDeviceOrientation[2]) + "\n\nscale\n" + scaleFormat.format(mOrthographicScale)); 209 | } 210 | } 211 | 212 | /** 213 | * determine the display rotation, initialize our display list components 214 | */ 215 | @SuppressLint("NewApi") 216 | private void init() { 217 | // determine rotation of the display, which determines how we rotate our 218 | // components. 219 | Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 220 | mDisplayRotation = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) ? display.getRotation() : display.getOrientation(); 221 | 222 | // establish our display list of components to draw 223 | mDisplayList.add(mCompassComponent); 224 | mDisplayList.add(mReticleComponent); 225 | 226 | // initialize our projection matricies 227 | mOrthographicProjectionMatrix.setToOrtho2D(0, 0, 1, -1); 228 | mPerspectiveProjectionMatrix.setToProjection(1 - mViewAspectRatio, 1000, mPerspectiveFOV, mViewAspectRatio); 229 | } 230 | 231 | @Override 232 | protected void onDraw(Canvas canvas) { 233 | synchronized (mPublishingLock) { 234 | for (DrawingComponent drawingComponent : mDisplayList) { 235 | drawingComponent.draw(canvas, mDrawRadius, (int) -mDerivedDeviceOrientation[2], canvas.getWidth(), canvas.getHeight(), p, p); 236 | } 237 | } 238 | } 239 | 240 | /** 241 | * With rotationMatrix, rotate the view components - we are assuming a fixed 242 | * camera looking down the origin, and rotating the world around us. 243 | * 244 | * @param rotationMatrix 245 | */ 246 | public void rotateView(Matrix4 rotationMatrix) { 247 | int width = getWidth(); 248 | int height = getHeight(); 249 | 250 | // Reset all components to their starting values, with each update we 251 | // calculate anew 252 | mPerspectiveVertexBatch.clear(); 253 | mOrthographicVertexBatch.clear(); 254 | mCompassComponent.resetPoints(); 255 | 256 | for (DrawingComponent drawingComponent : mDisplayList) { 257 | drawingComponent.addTo(mPerspectiveProjection ? mPerspectiveVertexBatch : mOrthographicVertexBatch); 258 | } 259 | 260 | // convention, set our model view matrix to identity 261 | mModelViewMatrix.idt(); 262 | 263 | // Translations for the perspective projection 264 | if (!mPerspectiveVertexBatch.isEmpty()) { 265 | mModelViewMatrix.mul(mPerspectiveProjectionMatrix).mul(rotationMatrix); 266 | // Projection matrix creates a perspective projection - 267 | // this maps a value into vector.w; x, y are divided by w to produce 268 | // perspective corrected x, y values. 269 | // the range of z is arbitrarily specified by us and depends on how 270 | // much resolution we want. 271 | // 272 | // We multiply by our rotation matrix. Matrix multiplication is done 273 | // in reverse order when represented this way. Perspective is 274 | // logically last. 275 | // 276 | // Then we scale and translate: 277 | // add 1, and mult by 0.5, width / height - this translates 0,0 to 278 | // 0.5, 0.5, moving the origin to the center of the view 279 | // 280 | // For 0, 90, 270, we make the assumption that our rotation matrix 281 | // is already taking this device position into account. 282 | // this is achieved by re-mapping the sensor coordinate systems in 283 | // these cases. 284 | switch (mDisplayRotation) { 285 | case Surface.ROTATION_0: 286 | case Surface.ROTATION_90: 287 | case Surface.ROTATION_270: 288 | for (Vector3 v : mPerspectiveVertexBatch) { 289 | vTemp.set(v.x, -v.y, -v.z, 0); 290 | vTemp.mul(mModelViewMatrix); 291 | v.x = (vTemp.x / vTemp.w + 1.0f) * 0.5f * width; 292 | v.y = height - (vTemp.y / vTemp.w + 1.0f) * 0.5f * height; 293 | v.z = vTemp.z; 294 | } 295 | break; 296 | 297 | // For 180, we have to reflect x and y values 298 | // TODO: find a way to eliminate the need for this branch? I was not 299 | // able to successfully re-map the sensor coordinate systems to 300 | // accomplish this. 301 | case Surface.ROTATION_180: 302 | for (Vector3 v : mPerspectiveVertexBatch) { 303 | vTemp.set(v.x, -v.y, -v.z, 0); 304 | vTemp.mul(mPerspectiveProjectionMatrix); 305 | // reflect x and y axes... 306 | v.x = (-vTemp.x / vTemp.w + 1.0f) * 0.5f * width; 307 | v.y = height - (-vTemp.y / vTemp.w + 1.0f) * 0.5f * height; 308 | v.z = vTemp.z; 309 | } 310 | break; 311 | } 312 | } 313 | 314 | // Translations for the orthographic projection 315 | if (!mOrthographicVertexBatch.isEmpty()) { 316 | mModelViewMatrix.mul(mOrthographicProjectionMatrix).mul(rotationMatrix); 317 | // Then we scale and translate, with the origin at the center of the 318 | // device display 319 | // Here, orthographic scale is simply how "zoomed in" we are to the 320 | // orthographic view 321 | // 322 | // For 0, 90, 270, we make the assumption that our rotation matrix 323 | // is already taking this device position into account. 324 | // this is achieved by re-mapping the sensor coordinate systems in 325 | // these cases. 326 | switch (mDisplayRotation) { 327 | case Surface.ROTATION_0: 328 | case Surface.ROTATION_90: 329 | case Surface.ROTATION_270: 330 | for (Vector3 v : mOrthographicVertexBatch) { 331 | vTemp.set(v.x, -v.y, -v.z, 0); 332 | vTemp.mul(mModelViewMatrix); 333 | v.x = (vTemp.x) * 0.5f * width * mOrthographicScale + width / 2; 334 | v.y = (vTemp.y) * 0.5f * width * mOrthographicScale + width / 2 + (height - width) / 2; 335 | v.z = vTemp.z * 0.5f * width * mOrthographicScale; 336 | } 337 | break; 338 | // For 180, we have to reflect x and y values 339 | // TODO: find a way to eliminate the need for this branch? I was not 340 | // able to successfully re-map the sensor coordinate systems to 341 | // accomplish this. 342 | case Surface.ROTATION_180: 343 | for (Vector3 v : mOrthographicVertexBatch) { 344 | vTemp.set(v.x, -v.y, -v.z, 0); 345 | vTemp.mul(mModelViewMatrix); 346 | // reflect x and y axes... 347 | v.x = (-vTemp.x) * 0.5f * width * mOrthographicScale + width / 2; 348 | v.y = (-vTemp.y) * 0.5f * width * mOrthographicScale + width / 2 + (height - width) / 2; 349 | v.z = vTemp.z * 0.5f * width * mOrthographicScale; 350 | } 351 | break; 352 | } 353 | } 354 | // prepare for drawing - ask components to prepare for drawing; publish 355 | // any modified values. 356 | synchronized (mPublishingLock) { 357 | for (DrawingComponent drawingComponent : mDisplayList) { 358 | drawingComponent.prepareDraw(); 359 | } 360 | 361 | // derive the orientation of the device based on where the back of 362 | // the phone is pointing, from our rotated points 363 | // TODO: find some more efficient/correct implementation of doing 364 | // this. 365 | mOrientationCalculator.getOrientation(rotationMatrix, mDisplayRotation, mDerivedDeviceOrientation); 366 | } 367 | 368 | // update orientation display 369 | updateOrientationDisplay(); 370 | 371 | // ask system for update of our view 372 | invalidate(); 373 | } 374 | 375 | /** 376 | * Calculate the smallest drawing radius and aspect ratio when this is 377 | * called by the system during layout 378 | */ 379 | @Override 380 | public void onSizeChanged(int w, int h, int oldw, int oldh) { 381 | super.onSizeChanged(w, h, oldw, oldh); 382 | mViewAspectRatio = (float) w / (float) h; 383 | mDrawRadius = Math.min(w, h) * 0.5f; 384 | updatePerspectiveProjectionMatrix(); 385 | log("onSizeChanged(): " + w + " x " + h + " aspect: " + mViewAspectRatio); 386 | } 387 | 388 | /** 389 | * Calculate the smallest drawing radius and aspect ratio when this is 390 | * called by the system during layout 391 | */ 392 | @Override 393 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 394 | setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)); 395 | mDrawRadius = Math.min(getMeasuredWidth(), getMeasuredHeight()) * 0.5f; 396 | mViewAspectRatio = (float) getMeasuredWidth() / (float) getMeasuredHeight(); 397 | updatePerspectiveProjectionMatrix(); 398 | log("onMeasure(): " + getMeasuredWidth() + " x " + getMeasuredHeight() + " aspect: " + mViewAspectRatio); 399 | } 400 | 401 | /** 402 | * updates our perspective projection matrix with the current field of view 403 | * and view aspect ratio 404 | */ 405 | private void updatePerspectiveProjectionMatrix() { 406 | mPerspectiveProjectionMatrix.setToProjection(1 - mViewAspectRatio, 1000, mPerspectiveFOV, mViewAspectRatio); 407 | } 408 | 409 | /** 410 | * logging if debug enabled 411 | * 412 | * @param msg 413 | */ 414 | private void log(String msg) { 415 | if (FLAG_DEBUG) { 416 | Log.e(TAG, msg); 417 | } 418 | } 419 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/RotationVectorCompass.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.pm.ActivityInfo; 7 | import android.content.pm.PackageManager; 8 | import android.hardware.Camera; 9 | import android.hardware.Camera.CameraInfo; 10 | import android.hardware.Sensor; 11 | import android.hardware.SensorManager; 12 | import android.os.Build; 13 | import android.os.Bundle; 14 | import android.util.Log; 15 | import android.view.Display; 16 | import android.view.Surface; 17 | import android.view.View; 18 | import android.view.Window; 19 | import android.view.WindowManager; 20 | import android.widget.CompoundButton; 21 | import android.widget.LinearLayout; 22 | import android.widget.RelativeLayout.LayoutParams; 23 | import android.widget.SeekBar; 24 | import android.widget.TextView; 25 | import android.widget.Toast; 26 | import android.widget.ToggleButton; 27 | 28 | import com.adamratana.rotationvectorcompass.camera.CameraPreviewLayer; 29 | import com.adamratana.rotationvectorcompass.camera.CameraUtil; 30 | import com.adamratana.rotationvectorcompass.math.Matrix4; 31 | import com.adamratana.rotationvectorcompass.rotation.MagAccelListener; 32 | import com.adamratana.rotationvectorcompass.rotation.RotationUpdateDelegate; 33 | import com.adamratana.rotationvectorcompass.rotation.RotationVectorListener; 34 | 35 | /** 36 | * An Augmented Reality Compass using the Rotation Vector Sensor 37 | * 38 | * This project is a proof of concept for various experiments involving the 39 | * Rotation Vector Sensor, Augmented Reality, overlaying these on top of a 40 | * Camera Preview, and using the Canvas API for drawing. 41 | * 42 | * - Uses the Canvas API for drawing: Why the canvas API? Because the canvas is 43 | * easy to understand for most people with little to no graphics programming 44 | * experience and to those with no knowledge of OpenGL. 45 | * 46 | * In addition we manually perform rotation and translation and vector 47 | * manipulation, which can serve as an introduction to these concepts. 48 | * 49 | * - Uses Orthographic and Perspective Projections: As an academic display of 50 | * how both work, to compare and contrast, especially for those who many be 51 | * unfamiliar. 52 | * 53 | * - open-source LibGDX for certain math classes Uses LibGDX for Matrix, 54 | * Quaternion, Vector3 classes, Vector4 derived from Vector3 55 | * 56 | * - Uses the Rotation Vector virtual Sensor: This has been little-documented, 57 | * and the documentation might not make much sense to someone without 3d 58 | * graphics or OpenGL experience. We show it in use and in contrast to the 59 | * typical sensor fusion of Magnetometer / Accelerometer. 60 | * 61 | * Some devices may not properly implement this, the galaxy tab 10.1 (version 1) 62 | * does not seem to have a consistent implementation. 63 | * 64 | * - Has a basic, filtered Sensor Fusion implementation: Uses the Magnetometer / 65 | * Accelerometer to detect device rotation, with a simple filter to smooth the 66 | * values. 67 | * 68 | * - Aims to provide an accurate representation of device orientation in 3d with 69 | * respect to North: A brute-force approach, but using typical API methods such 70 | * as SensorManager.getOrientation() do not provide acceptable values for all 71 | * positions 72 | * 73 | * - Camera preview integration, with portrait mode support: Overlays the 74 | * compass on top of the camera preview Uses the camera reported field of view 75 | * information to properly apply perspective to the compass Supports the Camera 76 | * when the devices is held in Portrait mode, could find few working examples of 77 | * this Employing fixes for known device abberations in the wild. - some devices 78 | * report 0 for field of view - ZTE Blade crashes while calling 79 | * Camera.Parameters.getHorizontalViewAngle() - some devices when rotating the 80 | * camera display by 0 degrees do not behave correctly 81 | * 82 | * - Supports Variable Device Orientation, Full Screen Allows the rotation of 83 | * the device, rather than locking to portrait or landscape Programatically 84 | * allows for locking of the current orientation Programatically allows for 85 | * toggling of full screen display 86 | * 87 | * - Conditional support back to 2.1 Branching is applied for API calls which 88 | * are incompatible with 2.1, 2.2 - setting the camera display rotation is not 89 | * possible in 2.1 - getting the camera field of view is also not possible in 90 | * 2.1 - Rotation Vector not available prior to 2.3 91 | * 92 | * @author Adam 93 | * 94 | */ 95 | public class RotationVectorCompass extends Activity implements RotationUpdateDelegate, CameraPreviewLayer.FOVUpdateDelegate { 96 | private static final boolean FLAG_DEBUG = true; 97 | private static final String TAG = "RotationVectorCompass"; 98 | 99 | private static final float MAX_FOV = 175f; 100 | private static final float MIN_FOV = 10f; 101 | private static final float DEFAULT_FOV = 40f; 102 | 103 | private static final float MAX_ORTHO_SCALE = 7.0f; 104 | private static final float DEFAULT_SCALE = 1.0f; 105 | private static final float MIN_ORTHO_SCALE = 0.25f; 106 | 107 | private OverlayView mOverlayView; 108 | private Matrix4 mRotationMatrix = new Matrix4(); 109 | private SensorManager mSensorManager; 110 | 111 | private MagAccelListener mMagAccel; 112 | private RotationVectorListener mRotationVector; 113 | 114 | private int mDisplayRotation; 115 | private boolean mUseRotationVector = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD); 116 | 117 | private float mFOV = DEFAULT_FOV; 118 | private float mOrthographicScale = DEFAULT_SCALE; 119 | private SeekBar mScaleAndFOVSlider = null; 120 | 121 | /** camera stuff **/ 122 | private boolean mCameraOn = false; 123 | private LinearLayout mCameraViewHolder; 124 | private LayoutParams mCameraViewLayoutParams; 125 | private CameraPreviewLayer mPreview; 126 | private Camera mCamera; 127 | private int mNumCameras; 128 | private int mDefaultCameraID = -1; // The first rear facing camera 129 | private boolean mFullScreen = false; 130 | private boolean mPerspectiveProjection = true; 131 | private boolean mOrientationLocked = false; 132 | 133 | @SuppressLint("NewApi") 134 | @Override 135 | public void onCreate(Bundle savedInstanceState) { 136 | super.onCreate(savedInstanceState); 137 | 138 | // setup window decorations 139 | getWindow().requestFeature(Window.FEATURE_NO_TITLE); 140 | final Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 141 | mDisplayRotation = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) ? display.getRotation() : display.getOrientation(); 142 | 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 144 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 145 | 146 | log("display: " + display.getWidth() + " x " + display.getHeight()); 147 | } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { 148 | this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 149 | mFullScreen = true; 150 | mOrientationLocked = true; 151 | } 152 | 153 | if (mFullScreen) { 154 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 155 | } 156 | 157 | setContentView(R.layout.main); 158 | 159 | // sensor listeners 160 | mMagAccel = new MagAccelListener(this); 161 | mRotationVector = new RotationVectorListener(this); 162 | mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 163 | 164 | // overlay view 165 | mOverlayView = (OverlayView) findViewById(R.id.rotateView2); 166 | mOverlayView.setFOVView((TextView) findViewById(R.id.fov_text)); 167 | mOverlayView.setPerspectiveProjection(mPerspectiveProjection); 168 | mOverlayView.setFOV(DEFAULT_FOV); 169 | 170 | // seekbar which controls fov (perspective) and scale (orthographic) 171 | mScaleAndFOVSlider = (SeekBar) findViewById(R.id.fov_bar); 172 | mScaleAndFOVSlider.setProgress(mPerspectiveProjection ? (int) ((mFOV - MIN_FOV) / (MAX_FOV - MIN_FOV) * 1000f) : (int) ((mOrthographicScale - MIN_ORTHO_SCALE) / (MAX_ORTHO_SCALE - MIN_ORTHO_SCALE) * 1000f)); 173 | 174 | // Orientation Lock Toggle 175 | final ToggleButton lockOrientationToggleButton = (ToggleButton) findViewById(R.id.lock_toggle); 176 | lockOrientationToggleButton.setChecked(mOrientationLocked); 177 | if (mOrientationLocked) { 178 | lockOrientationToggleButton.setVisibility(View.GONE); 179 | } 180 | lockOrientationToggleButton.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() { 181 | @Override 182 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 183 | mOrientationLocked = isChecked; 184 | if (mOrientationLocked) { 185 | if (display.getWidth() > display.getHeight()) { 186 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 187 | } else { 188 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 189 | } 190 | Toast.makeText(getApplicationContext(), "Locking orientation, this can't be undone", Toast.LENGTH_SHORT).show(); 191 | lockOrientationToggleButton.setVisibility(View.GONE); 192 | } 193 | } 194 | }); 195 | 196 | // Full Screen Toggle 197 | final ToggleButton fullScreenToggleButton = (ToggleButton) findViewById(R.id.fs_toggle); 198 | if (mFullScreen) { 199 | fullScreenToggleButton.setVisibility(View.GONE); 200 | } else { 201 | fullScreenToggleButton.setChecked(mFullScreen); 202 | fullScreenToggleButton.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() { 203 | @Override 204 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 205 | mFullScreen = isChecked; 206 | Toast.makeText(getApplicationContext(), mFullScreen ? "Full Screen ON" : "Full Screen OFF", Toast.LENGTH_SHORT).show(); 207 | 208 | if (mFullScreen) { 209 | // go full screen 210 | WindowManager.LayoutParams attrs = getWindow().getAttributes(); 211 | attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 212 | getWindow().setAttributes(attrs); 213 | } else { 214 | // go non-full screen 215 | WindowManager.LayoutParams attrs = getWindow().getAttributes(); 216 | attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); 217 | getWindow().setAttributes(attrs); 218 | } 219 | } 220 | 221 | }); 222 | } 223 | 224 | // Camera Preview Toggle 225 | final ToggleButton cameraToggleButton = (ToggleButton) findViewById(R.id.cam_toggle); 226 | cameraToggleButton.setChecked(mCameraOn); 227 | cameraToggleButton.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() { 228 | 229 | @Override 230 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 231 | mCameraOn = isChecked; 232 | if (mCameraOn) { 233 | startCamera(); 234 | } else { 235 | stopCamera(); 236 | } 237 | } 238 | 239 | }); 240 | 241 | // Rotation Vector / Mag Accel Toggle 242 | final ToggleButton rotationVectorToggleButton = (ToggleButton) findViewById(R.id.sensor_toggle); 243 | rotationVectorToggleButton.setChecked(mUseRotationVector); 244 | rotationVectorToggleButton.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() { 245 | @Override 246 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 247 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 248 | mUseRotationVector = isChecked; 249 | Toast.makeText(getApplicationContext(), mUseRotationVector ? "Rotation Vector Sensor" : "Magnetometer/Accelerometer Sensors", Toast.LENGTH_SHORT).show(); 250 | applySensors(mUseRotationVector); 251 | } 252 | } 253 | 254 | }); 255 | 256 | // Perspective / Orthographic Toggle 257 | final ToggleButton perspectiveProjectionToggleButton = (ToggleButton) findViewById(R.id.perspective_toggle); 258 | perspectiveProjectionToggleButton.setChecked(mPerspectiveProjection); 259 | perspectiveProjectionToggleButton.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() { 260 | @Override 261 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 262 | mPerspectiveProjection = isChecked; 263 | mOverlayView.setPerspectiveProjection(mPerspectiveProjection); 264 | mScaleAndFOVSlider.setProgress(mPerspectiveProjection ? (int) ((mFOV - MIN_FOV) / (MAX_FOV - MIN_FOV) * 1000f) : (int) ((mOrthographicScale - MIN_ORTHO_SCALE) / (MAX_ORTHO_SCALE - MIN_ORTHO_SCALE) * 1000f)); 265 | Toast.makeText(getApplicationContext(), mPerspectiveProjection ? "Perspective Projection" : "Orthographic Projection", Toast.LENGTH_SHORT).show(); 266 | } 267 | }); 268 | 269 | // Slider for adjusting Scale / FOV 270 | mScaleAndFOVSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 271 | @Override 272 | public void onStopTrackingTouch(SeekBar seekBar) { 273 | } 274 | 275 | @Override 276 | public void onStartTrackingTouch(SeekBar seekBar) { 277 | } 278 | 279 | @Override 280 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 281 | if (mPerspectiveProjection) { 282 | mFOV = MIN_FOV + progress / 1000.0f * (MAX_FOV - MIN_FOV); 283 | mOverlayView.setFOV(mFOV); 284 | } else { 285 | mOrthographicScale = MIN_ORTHO_SCALE + progress / 1000.0f * (MAX_ORTHO_SCALE - MIN_ORTHO_SCALE); 286 | mOverlayView.setOrthograhicScale(mOrthographicScale); 287 | } 288 | } 289 | }); 290 | 291 | // Camera Setup 292 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { 293 | // Find the total number of cameras available 294 | mNumCameras = Camera.getNumberOfCameras(); 295 | 296 | // Find the ID of the back-facing camera 297 | CameraInfo cameraInfo = new CameraInfo(); 298 | for (int i = 0; i < mNumCameras; i++) { 299 | Camera.getCameraInfo(i, cameraInfo); 300 | if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) { 301 | mDefaultCameraID = i; 302 | } 303 | } 304 | } else { 305 | // to work on non-froyo 306 | mDefaultCameraID = 1; 307 | } 308 | mCameraViewHolder = (LinearLayout) findViewById(R.id.cameraViewHolder); 309 | } 310 | 311 | private void applySensors(boolean useRV) { 312 | mSensorManager.unregisterListener(mMagAccel); 313 | mSensorManager.unregisterListener(mRotationVector); 314 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && useRV) { 315 | mSensorManager.registerListener(mRotationVector, mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR), SensorManager.SENSOR_DELAY_GAME); 316 | } else { 317 | mSensorManager.registerListener(mMagAccel, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME); 318 | mSensorManager.registerListener(mMagAccel, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME); 319 | } 320 | } 321 | 322 | private void updateViews() { 323 | mOverlayView.rotateView(mRotationMatrix); 324 | } 325 | 326 | private void stopCamera() { 327 | if (mCamera != null) { 328 | mPreview.setCamera(null); 329 | mCamera.release(); 330 | mCamera = null; 331 | 332 | mCameraViewHolder.removeView(mPreview); 333 | mPreview = null; 334 | } 335 | } 336 | 337 | private void startCamera() { 338 | if (mCamera != null) { 339 | return; 340 | } 341 | /** camera stuff **/ 342 | // Open the default i.e. the first rear facing camera. 343 | try { 344 | if (mDefaultCameraID != -1) { 345 | // new 346 | mCameraViewLayoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); 347 | mPreview = new CameraPreviewLayer(this, this); 348 | 349 | // create preview and attach to view -- will be removed when 350 | // pausing, will be recreated when resuming 351 | PackageManager pm = getPackageManager(); 352 | if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) && pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)) { 353 | mPreview.setCanAutoFocus(true); 354 | } else { 355 | mPreview.setCanAutoFocus(false); 356 | } 357 | 358 | mCameraViewHolder.addView(mPreview, mCameraViewLayoutParams); 359 | 360 | mCamera = Camera.open(); 361 | 362 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { 363 | CameraUtil.setCameraDisplayOrientation(this, mDefaultCameraID, mCamera); 364 | } 365 | mPreview.setCamera(mCamera); 366 | } 367 | } catch (Exception e) { 368 | log("startCamera(): exception caught: " + e); 369 | } 370 | } 371 | 372 | /** 373 | * logging if debug enabled 374 | * 375 | * @param msg 376 | */ 377 | private void log(String msg) { 378 | if (FLAG_DEBUG) { 379 | Log.e(TAG, msg); 380 | } 381 | } 382 | 383 | // RotationUpdateDelegate methods 384 | @Override 385 | public void onRotationUpdate(float[] newMatrix) { 386 | // remap matrix values according to display rotation, as in 387 | // SensorManager documentation. 388 | switch (mDisplayRotation) { 389 | case Surface.ROTATION_0: 390 | case Surface.ROTATION_180: 391 | break; 392 | case Surface.ROTATION_90: 393 | SensorManager.remapCoordinateSystem(newMatrix, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, newMatrix); 394 | break; 395 | case Surface.ROTATION_270: 396 | SensorManager.remapCoordinateSystem(newMatrix, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, newMatrix); 397 | break; 398 | default: 399 | break; 400 | } 401 | mRotationMatrix.set(newMatrix); 402 | updateViews(); 403 | } 404 | 405 | // CameraPreviewLayer.FOVDelegate methods 406 | @Override 407 | public void onFOVUpdate(int width, int height, float fovH, float fovV, float adjustedFOVH, float adjustedFOVV) { 408 | log("adjusted FOV for " + width + " x " + height + " h: " + fovH + " v: " + fovV + " adjH: " + adjustedFOVH + " adjV: " + adjustedFOVV); 409 | if (width > height) { 410 | mFOV = adjustedFOVH; 411 | } else { 412 | mFOV = adjustedFOVV; 413 | } 414 | mOverlayView.setFOV(mFOV); 415 | if (mPerspectiveProjection) { 416 | mScaleAndFOVSlider.setProgress((int) ((mFOV - MIN_FOV) / (MAX_FOV - MIN_FOV) * 1000f)); 417 | } 418 | } 419 | 420 | // Other Activity life-cycle methods 421 | @Override 422 | protected void onPause() { 423 | super.onPause(); 424 | mSensorManager.unregisterListener(mMagAccel); 425 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && mUseRotationVector) { 426 | mSensorManager.unregisterListener(mRotationVector); 427 | } 428 | if (mCameraOn) { 429 | stopCamera(); 430 | } 431 | } 432 | 433 | @Override 434 | protected void onResume() { 435 | super.onResume(); 436 | applySensors(mUseRotationVector); 437 | if (mCameraOn) { 438 | startCamera(); 439 | } 440 | } 441 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/camera/CameraPreviewLayer.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.camera; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import android.annotation.SuppressLint; 7 | import android.content.Context; 8 | import android.hardware.Camera; 9 | import android.hardware.Camera.Size; 10 | import android.os.Build; 11 | import android.util.Log; 12 | import android.view.MotionEvent; 13 | import android.view.SurfaceHolder; 14 | import android.view.SurfaceView; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | 18 | /** 19 | * A simple wrapper around a Camera and a SurfaceView that renders a centered 20 | * preview of the Camera to the surface. We need to center the SurfaceView 21 | * because not all devices have cameras that support preview sizes at the same 22 | * aspect ratio as the device's display. 23 | */ 24 | @SuppressLint({ "NewApi", "ViewConstructor" }) 25 | public class CameraPreviewLayer extends ViewGroup implements SurfaceHolder.Callback { 26 | private static final String TAG = "CameraPreviewLayer"; 27 | private static final boolean FLAG_DEBUG = true; 28 | private SurfaceView mSurfaceView; 29 | private SurfaceHolder mHolder; 30 | private Size mPreviewSize; 31 | private List mSupportedPreviewSizes; 32 | private Camera mCamera; 33 | private int mCurrentSurfaceWidth = 0; 34 | private int mCurrentSurfaceHeight = 0; 35 | private boolean mForceLayout = false; 36 | private boolean mCanAutoFocus = false; 37 | private boolean mHasSurface = false; 38 | private static final int FOCUS_NOT_STARTED = 0; 39 | private static final int FOCUSING = 1; 40 | private FOVUpdateDelegate mFOVDelegate; 41 | private int mFocusState = FOCUS_NOT_STARTED; 42 | 43 | public void setCanAutoFocus(boolean b) { 44 | mCanAutoFocus = b; 45 | } 46 | 47 | private final Camera.AutoFocusCallback mAutoFocusCallback = new Camera.AutoFocusCallback() { 48 | @Override 49 | public void onAutoFocus(boolean success, Camera camera) { 50 | // done 51 | mFocusState = FOCUS_NOT_STARTED; 52 | } 53 | }; 54 | 55 | public CameraPreviewLayer(Context context, FOVUpdateDelegate fovDelegate) { 56 | super(context); 57 | mFOVDelegate = fovDelegate; 58 | mSurfaceView = new SurfaceView(context); 59 | addView(mSurfaceView); 60 | 61 | // Install a SurfaceHolder.Callback so we get notified when the 62 | // underlying surface is created and destroyed. 63 | mHolder = mSurfaceView.getHolder(); 64 | mHolder.addCallback(this); 65 | mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 66 | } 67 | 68 | /** 69 | * Updates the reported field of view based on the aspect ratio of the 70 | * surface, calls back to the FOVDelegate 71 | * 72 | * @param width 73 | * @param height 74 | */ 75 | private void updateFOV(int width, int height) { 76 | if (mCamera != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { 77 | Camera.Parameters p = mCamera.getParameters(); 78 | 79 | int zoom = 100; 80 | 81 | if (p.isZoomSupported()) { 82 | if (p.getZoomRatios() != null) { 83 | if (p.getZoomRatios().get(p.getZoom()) != null) { 84 | zoom = p.getZoomRatios().get(p.getZoom()).intValue(); 85 | log("zoom: " + zoom); 86 | } 87 | } 88 | } 89 | if (zoom <= 0) { 90 | zoom = 100; 91 | } 92 | 93 | double aspect = (double) width / (double) height; 94 | if (width > height) { 95 | aspect = (double) height / (double) width; 96 | } 97 | 98 | float origHorizontalViewAngle = 0; 99 | float origVerticalViewAngle = 0; 100 | boolean correctlyReportedViewAngles = true; 101 | 102 | try { 103 | origHorizontalViewAngle = p.getHorizontalViewAngle(); 104 | } catch (Exception e) { 105 | correctlyReportedViewAngles = false; 106 | log("error getting horizontal view angle: " + e); 107 | } 108 | try { 109 | origVerticalViewAngle = p.getVerticalViewAngle(); 110 | } catch (Exception e) { 111 | correctlyReportedViewAngles = false; 112 | log("error getting vertical view angle: " + e); 113 | } 114 | 115 | log("camera reported view angle - h: " + origHorizontalViewAngle + " v: " + origVerticalViewAngle + " zoom: " + zoom + " correctly reported? " + correctlyReportedViewAngles); 116 | 117 | // fix for xperia reporting vfov as 0 118 | if (origVerticalViewAngle > 70 || origVerticalViewAngle < 10) { 119 | origHorizontalViewAngle = 51.2f; 120 | origVerticalViewAngle = 39.4f; 121 | correctlyReportedViewAngles = false; 122 | } 123 | 124 | log("final view angles - h: " + origHorizontalViewAngle + " v: " + origVerticalViewAngle + " zoom: " + zoom + " correctly reported? " + correctlyReportedViewAngles); 125 | 126 | double thetaV = Math.toRadians(origVerticalViewAngle); 127 | double thetaH = 2d * Math.atan(aspect * Math.tan(thetaV / 2)); 128 | thetaV = 2d * Math.atan(100d * Math.tan(thetaV / 2d) / zoom); 129 | thetaH = 2d * Math.atan(100d * Math.tan(thetaH / 2d) / zoom); 130 | log("scaledWidth: " + width + " scaledHeight: " + height); 131 | log("adjusted FOV V: " + Math.toDegrees(thetaV)); 132 | log("adjusted FOV H: " + Math.toDegrees(thetaH)); 133 | 134 | mFOVDelegate.onFOVUpdate(width, height, origHorizontalViewAngle, origVerticalViewAngle, (float) Math.toDegrees(thetaH), (float) Math.toDegrees(thetaV)); 135 | } 136 | } 137 | 138 | /** 139 | * Handle screen touch events, in this case, focus when we touch, stop when 140 | * we let go 141 | * 142 | * @param ev 143 | * @return 144 | */ 145 | public boolean handleTouchEvent(MotionEvent ev) { 146 | if (ev.getAction() == MotionEvent.ACTION_DOWN) { 147 | if (mHasSurface && mCamera != null && mCanAutoFocus && mFocusState != FOCUSING) { 148 | mFocusState = FOCUSING; 149 | // Log.e("SunSurveyor", "autofocusing!"); 150 | try { 151 | mCamera.autoFocus(mAutoFocusCallback); 152 | } catch (RuntimeException re) { 153 | try { 154 | mFocusState = FOCUS_NOT_STARTED; 155 | mCamera.cancelAutoFocus(); 156 | } catch (RuntimeException re2) { 157 | 158 | } 159 | } 160 | } 161 | } else if (ev.getAction() == MotionEvent.ACTION_UP) { 162 | if (mHasSurface && mCamera != null && mCanAutoFocus && mFocusState == FOCUSING) { 163 | mCamera.cancelAutoFocus(); 164 | mFocusState = FOCUS_NOT_STARTED; 165 | } 166 | } 167 | return super.onTouchEvent(ev); 168 | } 169 | 170 | public void stop() { 171 | mHolder.removeCallback(this); 172 | } 173 | 174 | public void setCamera(Camera camera) { 175 | mCamera = camera; 176 | if (mCamera != null) { 177 | mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes(); 178 | requestLayout(); 179 | } 180 | } 181 | 182 | /** 183 | * Given a camera, setup a surface to display the preview optimally, layout 184 | * accordingly 185 | * 186 | * @param camera 187 | */ 188 | public void setCameraHolderAndSurface(Camera camera) { 189 | mCamera = camera; 190 | mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes(); 191 | mPreviewSize = null; 192 | mForceLayout = true; 193 | setHolder(); 194 | setupSurface(); 195 | requestLayout(); 196 | } 197 | 198 | private void setHolder() { 199 | try { 200 | mCamera.setPreviewDisplay(mHolder); 201 | } catch (IOException ioe) { 202 | log("setHolder(): exception, can't set holder..."); 203 | } 204 | } 205 | 206 | private void setupSurface() { 207 | if (mCamera == null || mPreviewSize == null) { 208 | mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, mCurrentSurfaceWidth, mCurrentSurfaceHeight); 209 | } 210 | 211 | requestLayout(); 212 | 213 | // Now that the size is known, set up the camera parameters and begin 214 | // the preview. 215 | Camera.Parameters parameters = mCamera.getParameters(); 216 | if (mPreviewSize != null) { 217 | log("setupSurface(): using preview size: " + mPreviewSize.width + " / " + mPreviewSize.height); 218 | parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height); 219 | } 220 | mCamera.setParameters(parameters); 221 | mCamera.startPreview(); 222 | } 223 | 224 | private Size getOptimalPreviewSize(List sizes, int w, int h) { 225 | final double ASPECT_TOLERANCE = 0.1; 226 | double targetRatio = (double) w / h; 227 | if (sizes == null) 228 | return null; 229 | 230 | Size optimalSize = null; 231 | double minDiff = Double.MAX_VALUE; 232 | 233 | int targetHeight = h; 234 | 235 | // Try to find an size match aspect ratio and size 236 | for (Size size : sizes) { 237 | double ratio = (double) size.width / size.height; 238 | if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) 239 | continue; 240 | if (Math.abs(size.height - targetHeight) < minDiff) { 241 | optimalSize = size; 242 | minDiff = Math.abs(size.height - targetHeight); 243 | } 244 | } 245 | 246 | // Cannot find the one match the aspect ratio, ignore the requirement 247 | if (optimalSize == null) { 248 | minDiff = Double.MAX_VALUE; 249 | for (Size size : sizes) { 250 | if (Math.abs(size.height - targetHeight) < minDiff) { 251 | optimalSize = size; 252 | minDiff = Math.abs(size.height - targetHeight); 253 | } 254 | } 255 | } 256 | return optimalSize; 257 | } 258 | 259 | /** 260 | * logging if debug enabled 261 | * 262 | * @param msg 263 | */ 264 | private static void log(String msg) { 265 | if (FLAG_DEBUG) { 266 | Log.e(TAG, msg); 267 | } 268 | } 269 | 270 | // ViewGroup methods 271 | @Override 272 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 273 | // We purposely disregard child measurements because act as a 274 | // wrapper to a SurfaceView that centers the camera preview instead 275 | // of stretching it. 276 | final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); 277 | final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); 278 | setMeasuredDimension(width, height); 279 | 280 | if (mSupportedPreviewSizes != null) { 281 | if (width > height) { 282 | mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height); 283 | } else { 284 | mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, height, width); 285 | } 286 | } 287 | } 288 | 289 | @Override 290 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 291 | // When layout happens, find the right size for the camera preview, 292 | // based on the current width and height of the display (which can be 293 | // either portrait or landscape) 294 | log("onLayout()"); 295 | if ((mForceLayout || changed) && getChildCount() > 0) { 296 | mForceLayout = false; 297 | final View child = getChildAt(0); 298 | 299 | final int h = b - t; 300 | final int w = r - l; 301 | 302 | if (w == 0 || h == 0) { 303 | log("onLayout: width or height is 0, exiting"); 304 | return; 305 | } 306 | 307 | int width = w; 308 | int height = h; 309 | 310 | int previewWidth = width; 311 | int previewHeight = height; 312 | 313 | // accounting for rotated camera preview - the preview itself is 314 | // always w > h due to physical sensor 315 | if (mPreviewSize != null) { 316 | if (w < h) { 317 | previewWidth = mPreviewSize.height; 318 | previewHeight = mPreviewSize.width; 319 | } else { 320 | previewWidth = mPreviewSize.width; 321 | previewHeight = mPreviewSize.height; 322 | } 323 | } 324 | log("width: " + width + " height: " + height + " previewWidth: " + previewWidth + " previewHeight: " + previewHeight); 325 | int scaledWidth = 0; 326 | int scaledHeight = 0; 327 | 328 | // Center the child SurfaceView within the parent. 329 | if (width > height && width * previewHeight > height * previewWidth) { 330 | final int scaledChildWidth = previewWidth * height / previewHeight; 331 | int left = (width - scaledChildWidth) / 2; 332 | int right = (width + scaledChildWidth) / 2; 333 | int bot = 0; 334 | int top = height; 335 | child.layout(left, bot, right, top); 336 | scaledWidth = width; 337 | scaledHeight = height; 338 | 339 | } else { 340 | int scaledChildHeight = previewHeight * width / previewWidth; 341 | 342 | if (width < height) { 343 | scaledChildHeight = previewHeight * width / previewWidth; 344 | } 345 | 346 | int left = 0; 347 | int right = width; 348 | int bot = (height - scaledChildHeight) / 2; 349 | int top = (height + scaledChildHeight) / 2; 350 | 351 | if (scaledChildHeight > height) { 352 | if (width > height) { 353 | float ratio = (float) height / (float) scaledChildHeight; 354 | bot = 0; 355 | top = height; 356 | left = (int) ((width - (ratio * width)) / 2); 357 | right = width - left; 358 | } 359 | } 360 | 361 | child.layout(left, bot, right, top); 362 | scaledWidth = right - left; 363 | scaledHeight = top - bot; 364 | } 365 | updateFOV(scaledWidth, scaledHeight); 366 | } 367 | } 368 | 369 | // SurfaceHolder.Callback methods 370 | @Override 371 | public void surfaceCreated(SurfaceHolder holder) { 372 | // The Surface has been created, acquire the camera and tell it where 373 | // to draw. 374 | try { 375 | if (mCamera != null) { 376 | mCamera.setPreviewDisplay(holder); 377 | } 378 | } catch (IOException exception) { 379 | } 380 | 381 | // important for autofocus, we can't allow autofocus until the camera is 382 | // attached to the surface. 383 | mHasSurface = true; 384 | } 385 | 386 | @Override 387 | public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 388 | mCurrentSurfaceWidth = w; 389 | mCurrentSurfaceHeight = h; 390 | if (mCamera == null) { 391 | log("surfaceChanged(): camera is null, returning..."); 392 | return; 393 | } 394 | setupSurface(); 395 | } 396 | 397 | @Override 398 | public void surfaceDestroyed(SurfaceHolder holder) { 399 | mHasSurface = false; 400 | // Surface will be destroyed when we return, so stop the preview. 401 | if (mCamera != null) { 402 | mCamera.stopPreview(); 403 | mCamera.release(); 404 | } 405 | } 406 | 407 | /** 408 | * Delegate for receiving updates to the camera field of view Updates are 409 | * necessary when the screen rotates, the vertical field of view which 410 | * projections are based on will become the horizontal field of view of the 411 | * camera sensor, for instance 412 | * 413 | * @author Adam 414 | * 415 | */ 416 | public interface FOVUpdateDelegate { 417 | /** 418 | * called when field of view changes 419 | * 420 | * @param width 421 | * - width of the display surface 422 | * @param height 423 | * - height of the display surface 424 | * @param fovH 425 | * - original camera reported horizontal field of view 426 | * @param fovV 427 | * - original camera reported vertical field of view 428 | * @param adjustedFOVH 429 | * - adjusted horizontal field of view 430 | * @param adjustedFOVV 431 | * - adjusted vertical field of view 432 | */ 433 | public void onFOVUpdate(int width, int height, float fovH, float fovV, float adjustedFOVH, float adjustedFOVV); 434 | } 435 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/camera/CameraUtil.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.camera; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.hardware.Camera; 6 | import android.view.Surface; 7 | 8 | public class CameraUtil { 9 | 10 | /** 11 | * set the camera display orientation based on the activity's rotation 12 | * 13 | * @param activity 14 | * @param cameraId 15 | * @param camera 16 | */ 17 | @SuppressLint("NewApi") 18 | public static void setCameraDisplayOrientation(Activity activity, int cameraId, android.hardware.Camera camera) { 19 | final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 20 | final int rotDeg = getCameraRotationForSurfaceRotation(rotation, camera, cameraId); 21 | if (rotDeg != 0) { 22 | camera.setDisplayOrientation(rotDeg); 23 | } 24 | } 25 | 26 | /** 27 | * gets the correct rotation of the camera for the surface rotation 28 | * requested, adjust for front/back camera 29 | * 30 | * @param surfaceRotation 31 | * @param camera 32 | * @param cameraID 33 | * @return 34 | */ 35 | @SuppressLint("NewApi") 36 | public static int getCameraRotationForSurfaceRotation(int surfaceRotation, Camera camera, int cameraID) { 37 | android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); 38 | android.hardware.Camera.getCameraInfo(cameraID, info); 39 | 40 | int degrees = 0; 41 | 42 | switch (surfaceRotation) { 43 | case Surface.ROTATION_0: 44 | degrees = 0; 45 | break; 46 | case Surface.ROTATION_90: 47 | degrees = 90; 48 | break; 49 | case Surface.ROTATION_180: 50 | degrees = 180; 51 | break; 52 | case Surface.ROTATION_270: 53 | degrees = 270; 54 | break; 55 | } 56 | 57 | int result; 58 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 59 | result = (info.orientation + degrees) % 360; 60 | result = (360 - result) % 360; // compensate the mirror 61 | } else { // back-facing 62 | result = (info.orientation - degrees + 360) % 360; 63 | } 64 | return result; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/drawing/CompassComponent.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.drawing; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Paint.Align; 11 | import android.graphics.Paint.Style; 12 | import android.util.FloatMath; 13 | 14 | import com.adamratana.rotationvectorcompass.math.Vector3; 15 | 16 | /** 17 | * A Spherical compass representation, with the camera viewpoint inside the 18 | * sphere 19 | * 20 | * @author Adam 21 | * 22 | */ 23 | public class CompassComponent implements DrawingComponent { 24 | private static final int POINTS_PER_SEGMENT = 72; 25 | private static final int NUM_SEGMENTS = 11; 26 | private static final int NUM_POINTS = POINTS_PER_SEGMENT * NUM_SEGMENTS; 27 | private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f); 28 | private static boolean SHOW_TEXT = true; 29 | private static final String COMPASS_N = "N", COMPASS_S = "S", COMPASS_E = "E", COMPASS_W = "W"; 30 | 31 | private List mPublishedPoints = new ArrayList(NUM_POINTS + 1); 32 | private List mVertices = new ArrayList(NUM_POINTS + 1); 33 | 34 | private LineBatcher mLineBatcher = new LineBatcher(NUM_POINTS); 35 | 36 | private int mTextColor = Color.WHITE; 37 | 38 | // the compass degree text markers 39 | private static final String[] COMPASS_TEXT = new String[25]; 40 | 41 | static { 42 | for (int i = 0; i < 25; i++) { 43 | COMPASS_TEXT[i] = " " + ((i * 15) + "\u00B0"); 44 | } 45 | } 46 | 47 | public CompassComponent() { 48 | for (int i = 0; i < NUM_POINTS; i++) { 49 | mVertices.add(new Vector3()); 50 | mPublishedPoints.add(new Vector3()); 51 | } 52 | } 53 | 54 | @Override 55 | public void draw(Canvas canvas, float drawRadius, float textRotation, int xRange, int yRange, Paint paint, Paint textPaint) { 56 | paint.setStrokeWidth(2); 57 | 58 | Vector3 fp = null, pp = null; 59 | int pointsSeen = 0; 60 | boolean fpSeen = false, anyDrawn = false, horizonDrawn = false; 61 | 62 | // Horizontal Lines 63 | for (int i = 0; i < NUM_SEGMENTS; i++) { 64 | fpSeen = false; 65 | fp = null; 66 | pointsSeen = 0; 67 | for (int j = 0; j < POINTS_PER_SEGMENT; j++) { 68 | Vector3 v = mPublishedPoints.get(i * POINTS_PER_SEGMENT + j); 69 | if (v.z > 0) { 70 | if (j == 0) { 71 | fp = v; 72 | fpSeen = true; 73 | } 74 | if (pointsSeen > 0) { 75 | mLineBatcher.addLine(v.x, v.y, pp.x, pp.y); 76 | anyDrawn = true; 77 | if (j == POINTS_PER_SEGMENT - 1 && fpSeen) { 78 | mLineBatcher.addLine(fp.x, fp.y, v.x, v.y); 79 | } 80 | } 81 | pp = v; 82 | ++pointsSeen; 83 | } else { 84 | pointsSeen = 0; 85 | } 86 | } 87 | } 88 | 89 | // Vertical Lines 90 | for (int i = 0; i < (POINTS_PER_SEGMENT / 3); i++) { 91 | pointsSeen = 0; 92 | fp = null; 93 | fpSeen = false; 94 | for (int j = 0; j < NUM_SEGMENTS; j++) { 95 | Vector3 v = mPublishedPoints.get(j * POINTS_PER_SEGMENT + i * 3); 96 | if (v.z > 0) { 97 | if (j == 0) { 98 | fp = v; 99 | fpSeen = true; 100 | } 101 | if (pointsSeen > 0) { 102 | mLineBatcher.addLine(v.x, v.y, pp.x, pp.y); 103 | anyDrawn = true; 104 | if (j == POINTS_PER_SEGMENT - 1 && fpSeen) { 105 | mLineBatcher.addLine(fp.x, fp.y, v.x, v.y); 106 | } 107 | } 108 | pp = v; 109 | ++pointsSeen; 110 | } else { 111 | pointsSeen = 0; 112 | } 113 | } 114 | } 115 | 116 | // Render lines if any visible 117 | if (anyDrawn) { 118 | paint.setColor(Color.rgb(55, 181, 229)); 119 | paint.setStyle(Style.STROKE); 120 | mLineBatcher.drawLines(canvas, paint); 121 | } 122 | 123 | fp = null; 124 | fpSeen = false; 125 | pointsSeen = 0; 126 | // Vertical Line representing North 127 | for (int j = 0; j < NUM_SEGMENTS; j++) { 128 | Vector3 v = mPublishedPoints.get(j * POINTS_PER_SEGMENT); 129 | if (v.z > 0) { 130 | if (j == 0) { 131 | fp = v; 132 | fpSeen = true; 133 | } 134 | if (pointsSeen > 0) { 135 | mLineBatcher.addLine(v.x, v.y, pp.x, pp.y); 136 | anyDrawn = true; 137 | if (j == POINTS_PER_SEGMENT - 1 && fpSeen) { 138 | mLineBatcher.addLine(fp.x, fp.y, v.x, v.y); 139 | } 140 | } 141 | pp = v; 142 | ++pointsSeen; 143 | } else { 144 | pointsSeen = 0; 145 | } 146 | } 147 | 148 | if (anyDrawn) { 149 | paint.setColor(Color.RED); 150 | paint.setStyle(Style.STROKE); 151 | mLineBatcher.drawLines(canvas, paint); 152 | } 153 | 154 | // horizon line 155 | fp = null; 156 | fpSeen = false; 157 | pointsSeen = 0; 158 | for (int j = 0; j < POINTS_PER_SEGMENT; j++) { 159 | Vector3 v = mPublishedPoints.get(5 * POINTS_PER_SEGMENT + j); 160 | if (v.z > 0) { 161 | if (j == 0) { 162 | fp = v; 163 | fpSeen = true; 164 | } 165 | if (pointsSeen > 0) { 166 | mLineBatcher.addLine(v.x, v.y, pp.x, pp.y); 167 | horizonDrawn = true; 168 | if (j == POINTS_PER_SEGMENT - 1 && fpSeen) { 169 | mLineBatcher.addLine(fp.x, fp.y, v.x, v.y); 170 | } 171 | } 172 | pp = v; 173 | ++pointsSeen; 174 | } else { 175 | pointsSeen = 0; 176 | } 177 | } 178 | 179 | if (horizonDrawn) { 180 | paint.setColor(Color.RED); 181 | paint.setStyle(Style.STROKE); 182 | mLineBatcher.drawLines(canvas, paint); 183 | } 184 | 185 | // the compass text - degree markers, NSWE 186 | if (SHOW_TEXT) { 187 | textPaint.setTextSize(20); 188 | textPaint.setColor(mTextColor); 189 | textPaint.setTextAlign(Align.CENTER); 190 | 191 | for (int j = 0; j < POINTS_PER_SEGMENT; j++) { 192 | int angle = (int) (360.0 / POINTS_PER_SEGMENT * j); 193 | 194 | for (int k = 0; k < 11; k += 5) { 195 | int idx = k * POINTS_PER_SEGMENT + j; 196 | int angleIncrement = (k == 5 ? 15 : 30); 197 | Vector3 v = mPublishedPoints.get(idx); 198 | if (angle % angleIncrement == 0 && v.z > 0) { 199 | if (textRotation != 0) { 200 | canvas.save(); 201 | canvas.rotate(textRotation, v.x, v.y); 202 | } 203 | 204 | // draw circle and angle designation 205 | switch (angle) { 206 | case 0: 207 | textPaint.setTextSize(textPaint.getTextSize() * 3); 208 | int oldTextColor = textPaint.getColor(); 209 | textPaint.setColor(Color.RED); 210 | drawTextStrokeFill(canvas, COMPASS_N, v.x, v.y, textPaint); 211 | textPaint.setColor(oldTextColor); 212 | 213 | textPaint.setTextSize(textPaint.getTextSize() / 3); 214 | break; 215 | case 90: 216 | textPaint.setTextSize(textPaint.getTextSize() * 3); 217 | drawTextStrokeFill(canvas, COMPASS_E, v.x, v.y, textPaint); 218 | textPaint.setTextSize(textPaint.getTextSize() / 3); 219 | break; 220 | case 180: 221 | textPaint.setTextSize(textPaint.getTextSize() * 3); 222 | drawTextStrokeFill(canvas, COMPASS_S, v.x, v.y, textPaint); 223 | textPaint.setTextSize(textPaint.getTextSize() / 3); 224 | break; 225 | case 270: 226 | textPaint.setTextSize(textPaint.getTextSize() * 3); 227 | drawTextStrokeFill(canvas, COMPASS_W, v.x, v.y, textPaint); 228 | textPaint.setTextSize(textPaint.getTextSize() / 3); 229 | break; 230 | case 360: 231 | break; 232 | default: 233 | textPaint.setTextSize(textPaint.getTextSize() * 1.3f); 234 | drawTextStrokeFill(canvas, COMPASS_TEXT[angle / 15], v.x, v.y, textPaint); 235 | textPaint.setTextSize(textPaint.getTextSize() / 1.3f); 236 | break; 237 | } 238 | 239 | if (textRotation != 0) { 240 | canvas.restore(); 241 | } 242 | } 243 | } 244 | } 245 | } 246 | } 247 | 248 | /** 249 | * reset all points to their original positions on the sphere 250 | */ 251 | public void resetPoints() { 252 | for (int j = 0; j < NUM_SEGMENTS; j++) { 253 | int idx = j - 5; 254 | float jCosVal = FloatMath.cos(DEGREES_TO_RADIANS * (float) (idx * 15)); 255 | float jCosValInv = FloatMath.cos(DEGREES_TO_RADIANS * (float) (90 - idx * 15)); 256 | for (int i = 0; i < POINTS_PER_SEGMENT; i++) { 257 | float sinVal = FloatMath.sin(DEGREES_TO_RADIANS * (float) (i * (360 / POINTS_PER_SEGMENT))); 258 | float cosVal = FloatMath.cos(DEGREES_TO_RADIANS * (float) (i * (360 / POINTS_PER_SEGMENT))); 259 | mVertices.get(i + (POINTS_PER_SEGMENT * j)).set(sinVal * jCosVal * 1, -cosVal * jCosVal * 1, jCosValInv * 1); 260 | } 261 | } 262 | } 263 | 264 | /** 265 | * draws text surrounded by a black stroke 266 | * 267 | * @param canvas 268 | * @param text 269 | * @param x 270 | * @param y 271 | * @param p 272 | * - paint for drawing the text 273 | */ 274 | public static void drawTextStrokeFill(Canvas canvas, String text, float x, float y, Paint p) { 275 | int tempColor = p.getColor(); 276 | p.setColor(Color.BLACK); 277 | p.setStyle(Style.FILL_AND_STROKE); 278 | p.setStrokeWidth(p.getStrokeWidth() + 3); 279 | canvas.drawText(text, x, y, p); 280 | p.setStrokeWidth(p.getStrokeWidth() - 3); 281 | p.setColor(tempColor); 282 | p.setStyle(Style.FILL); 283 | canvas.drawText(text, x, y, p); 284 | } 285 | 286 | @Override 287 | public void prepareDraw() { 288 | // publish the points to be drawn, since any rotation/modification can 289 | // happen on a separate thread. 290 | final int size = mVertices.size(); 291 | for (int i = 0; i < size; i++) { 292 | mPublishedPoints.get(i).set(mVertices.get(i)); 293 | } 294 | } 295 | 296 | @Override 297 | public void addTo(Collection transformationCollection) { 298 | transformationCollection.addAll(mVertices); 299 | } 300 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/drawing/DrawingComponent.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.drawing; 2 | 3 | import java.util.Collection; 4 | 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | 8 | import com.adamratana.rotationvectorcompass.math.Vector3; 9 | 10 | public interface DrawingComponent { 11 | /** 12 | * Draws the component using the supplied canvas, radius, rotation and 13 | * paints. 14 | * 15 | * @param canvas 16 | * @param drawRadius 17 | * @param textRotation 18 | * - in degrees 19 | * @param xMax 20 | * - max X value considered valid 21 | * @param yMax 22 | * - max Y value considered valid 23 | * @param paint 24 | * @param textPaint 25 | * - to be used for any text output 26 | */ 27 | public void draw(Canvas canvas, float drawRadius, float textRotation, int xMax, int yMax, Paint paint, Paint textPaint); 28 | 29 | /** 30 | * Called before transforming vertices, add any vertices to be transformed 31 | * to the vertex batch 32 | * 33 | * @param vertexBatch 34 | */ 35 | public void addTo(Collection vertexBatch); 36 | 37 | /** 38 | * Called immediately before draw 39 | */ 40 | public void prepareDraw(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/drawing/LineBatcher.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.drawing; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | 6 | /** 7 | * Accumulates lines to draw in a batch for efficiency with hw-accelerated 8 | * canvas implementations 9 | * 10 | * @author Adam 11 | * 12 | */ 13 | public class LineBatcher { 14 | private final float[] mLineBuffer; 15 | private final int mMaxLines; 16 | private int mLineCount = 0; 17 | 18 | /** 19 | * Intializes the buffer to hold maxLines 20 | * 21 | * @param maxLines 22 | */ 23 | public LineBatcher(int maxLines) { 24 | mLineBuffer = new float[4 * maxLines]; 25 | mMaxLines = maxLines; 26 | } 27 | 28 | /** 29 | * Add a line to the current batch; ignores if maxLines already allocated 30 | * 31 | * @param x1 32 | * - from X 33 | * @param y1 34 | * - from Y 35 | * @param x2 36 | * - to X 37 | * @param y2 38 | * - to Y 39 | */ 40 | public void addLine(float x1, float y1, float x2, float y2) { 41 | if (mLineCount < mMaxLines) { 42 | mLineBuffer[mLineCount * 4] = x1; 43 | mLineBuffer[mLineCount * 4 + 1] = y1; 44 | mLineBuffer[mLineCount * 4 + 2] = x2; 45 | mLineBuffer[mLineCount * 4 + 3] = y2; 46 | ++mLineCount; 47 | } 48 | } 49 | 50 | /** 51 | * Draw the current batch of lines; clears batch when done 52 | * 53 | * @param canvas 54 | * @param paint 55 | */ 56 | public void drawLines(Canvas canvas, Paint paint) { 57 | if (mLineCount > 0) { 58 | canvas.drawLines(mLineBuffer, 0, mLineCount * 4, paint); 59 | mLineCount = 0; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/drawing/ReticleComponent.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.drawing; 2 | 3 | import java.util.Collection; 4 | 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Paint.Style; 9 | 10 | import com.adamratana.rotationvectorcompass.math.Vector3; 11 | 12 | /** 13 | * A target reticle, static, in the middle of the display 14 | * 15 | * @author Adam 16 | * 17 | */ 18 | public class ReticleComponent implements DrawingComponent { 19 | @Override 20 | public void draw(Canvas canvas, float drawRadius, float textRotation, int xRange, int yRange, Paint paint, Paint textPaint) { 21 | paint.setStrokeWidth(2); 22 | paint.setStyle(Style.STROKE); 23 | paint.setColor(Color.RED); 24 | canvas.drawCircle(0.5f * xRange, 0.5f * yRange, 0.1f * drawRadius, paint); 25 | canvas.drawLine(0.5f * xRange, 0.5f * yRange - 0.15f * drawRadius, 0.5f * xRange, 0.5f * yRange + 0.15f * drawRadius, paint); 26 | canvas.drawLine(0.5f * xRange - 0.15f * drawRadius, 0.5f * yRange, 0.5f * xRange + 0.15f * drawRadius, 0.5f * yRange, paint); 27 | } 28 | 29 | @Override 30 | public void addTo(Collection transformationCollection) { 31 | } 32 | 33 | @Override 34 | public void prepareDraw() { 35 | } 36 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/MathUtils.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | /******************************************************************************* 3 | * Copyright 2011 See AUTHORS file. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | ******************************************************************************/ 17 | /** from libgdx: https://github.com/libgdx/libgdx */ 18 | 19 | import java.util.Random; 20 | 21 | /** Utility and fast math functions. 22 | *

23 | * Thanks to Riven on JavaGaming.org for the basis of sin/cos/atan2/floor/ceil. 24 | * @author Nathan Sweet */ 25 | public class MathUtils { 26 | static public final float nanoToSec = 1 / 1000000000f; 27 | 28 | // --- 29 | 30 | static public final float PI = 3.1415927f; 31 | 32 | static private final int SIN_BITS = 13; // Adjust for accuracy. 33 | static private final int SIN_MASK = ~(-1 << SIN_BITS); 34 | static private final int SIN_COUNT = SIN_MASK + 1; 35 | 36 | static private final float radFull = PI * 2; 37 | static private final float degFull = 360; 38 | static private final float radToIndex = SIN_COUNT / radFull; 39 | static private final float degToIndex = SIN_COUNT / degFull; 40 | 41 | static public final float radiansToDegrees = 180f / PI; 42 | static public final float radDeg = radiansToDegrees; 43 | static public final float degreesToRadians = PI / 180; 44 | static public final float degRad = degreesToRadians; 45 | 46 | static private class Sin { 47 | static final float[] table = new float[SIN_COUNT]; 48 | static { 49 | for (int i = 0; i < SIN_COUNT; i++) 50 | table[i] = (float)Math.sin((i + 0.5f) / SIN_COUNT * radFull); 51 | for (int i = 0; i < 360; i += 90) 52 | table[(int)(i * degToIndex) & SIN_MASK] = (float)Math.sin(i * degreesToRadians); 53 | } 54 | } 55 | 56 | static private class Cos { 57 | static final float[] table = new float[SIN_COUNT]; 58 | static { 59 | for (int i = 0; i < SIN_COUNT; i++) 60 | table[i] = (float)Math.cos((i + 0.5f) / SIN_COUNT * radFull); 61 | for (int i = 0; i < 360; i += 90) 62 | table[(int)(i * degToIndex) & SIN_MASK] = (float)Math.cos(i * degreesToRadians); 63 | } 64 | } 65 | 66 | /** Returns the sine in radians. */ 67 | static public final float sin (float radians) { 68 | return Sin.table[(int)(radians * radToIndex) & SIN_MASK]; 69 | } 70 | 71 | /** Returns the cosine in radians. */ 72 | static public final float cos (float radians) { 73 | return Cos.table[(int)(radians * radToIndex) & SIN_MASK]; 74 | } 75 | 76 | /** Returns the sine in radians. */ 77 | static public final float sinDeg (float degrees) { 78 | return Sin.table[(int)(degrees * degToIndex) & SIN_MASK]; 79 | } 80 | 81 | /** Returns the cosine in radians. */ 82 | static public final float cosDeg (float degrees) { 83 | return Cos.table[(int)(degrees * degToIndex) & SIN_MASK]; 84 | } 85 | 86 | // --- 87 | 88 | static private final int ATAN2_BITS = 7; // Adjust for accuracy. 89 | static private final int ATAN2_BITS2 = ATAN2_BITS << 1; 90 | static private final int ATAN2_MASK = ~(-1 << ATAN2_BITS2); 91 | static private final int ATAN2_COUNT = ATAN2_MASK + 1; 92 | static final int ATAN2_DIM = (int)Math.sqrt(ATAN2_COUNT); 93 | static private final float INV_ATAN2_DIM_MINUS_1 = 1.0f / (ATAN2_DIM - 1); 94 | 95 | static private class Atan2 { 96 | static final float[] table = new float[ATAN2_COUNT]; 97 | static { 98 | for (int i = 0; i < ATAN2_DIM; i++) { 99 | for (int j = 0; j < ATAN2_DIM; j++) { 100 | float x0 = (float)i / ATAN2_DIM; 101 | float y0 = (float)j / ATAN2_DIM; 102 | table[j * ATAN2_DIM + i] = (float)Math.atan2(y0, x0); 103 | } 104 | } 105 | } 106 | } 107 | 108 | /** Returns atan2 in radians from a lookup table. */ 109 | static public final float atan2 (float y, float x) { 110 | float add, mul; 111 | if (x < 0) { 112 | if (y < 0) { 113 | y = -y; 114 | mul = 1; 115 | } else 116 | mul = -1; 117 | x = -x; 118 | add = -PI; 119 | } else { 120 | if (y < 0) { 121 | y = -y; 122 | mul = -1; 123 | } else 124 | mul = 1; 125 | add = 0; 126 | } 127 | float invDiv = 1 / ((x < y ? y : x) * INV_ATAN2_DIM_MINUS_1); 128 | int xi = (int)(x * invDiv); 129 | int yi = (int)(y * invDiv); 130 | return (Atan2.table[yi * ATAN2_DIM + xi] + add) * mul; 131 | } 132 | 133 | // --- 134 | 135 | static public Random random = new Random(); 136 | 137 | /** Returns a random number between 0 (inclusive) and the specified value (inclusive). */ 138 | static public final int random (int range) { 139 | return random.nextInt(range + 1); 140 | } 141 | 142 | /** Returns a random number between start (inclusive) and end (inclusive). */ 143 | static public final int random (int start, int end) { 144 | return start + random.nextInt(end - start + 1); 145 | } 146 | 147 | static public final boolean randomBoolean () { 148 | return random.nextBoolean(); 149 | } 150 | 151 | static public final float random () { 152 | return random.nextFloat(); 153 | } 154 | 155 | /** Returns a random number between 0 (inclusive) and the specified value (inclusive). */ 156 | static public final float random (float range) { 157 | return random.nextFloat() * range; 158 | } 159 | 160 | /** Returns a random number between start (inclusive) and end (inclusive). */ 161 | static public final float random (float start, float end) { 162 | return start + random.nextFloat() * (end - start); 163 | } 164 | 165 | // --- 166 | 167 | /** Returns the next power of two. Returns the specified value if the value is already a power of two. */ 168 | static public int nextPowerOfTwo (int value) { 169 | if (value == 0) return 1; 170 | value--; 171 | value |= value >> 1; 172 | value |= value >> 2; 173 | value |= value >> 4; 174 | value |= value >> 8; 175 | value |= value >> 16; 176 | return value + 1; 177 | } 178 | 179 | static public boolean isPowerOfTwo (int value) { 180 | return value != 0 && (value & value - 1) == 0; 181 | } 182 | 183 | // --- 184 | 185 | static public int clamp (int value, int min, int max) { 186 | if (value < min) return min; 187 | if (value > max) return max; 188 | return value; 189 | } 190 | 191 | static public short clamp (short value, short min, short max) { 192 | if (value < min) return min; 193 | if (value > max) return max; 194 | return value; 195 | } 196 | 197 | static public float clamp (float value, float min, float max) { 198 | if (value < min) return min; 199 | if (value > max) return max; 200 | return value; 201 | } 202 | 203 | // --- 204 | 205 | static private final int BIG_ENOUGH_INT = 16 * 1024; 206 | static private final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT; 207 | static private final double CEIL = 0.9999999; 208 | static private final double BIG_ENOUGH_CEIL = NumberUtils 209 | .longBitsToDouble(NumberUtils.doubleToLongBits(BIG_ENOUGH_INT + 1) - 1); 210 | static private final double BIG_ENOUGH_ROUND = BIG_ENOUGH_INT + 0.5f; 211 | 212 | /** Returns the largest integer less than or equal to the specified float. This method will only properly floor floats from 213 | * -(2^14) to (Float.MAX_VALUE - 2^14). */ 214 | static public int floor (float x) { 215 | return (int)(x + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT; 216 | } 217 | 218 | /** Returns the largest integer less than or equal to the specified float. This method will only properly floor floats that are 219 | * positive. Note this method simply casts the float to int. */ 220 | static public int floorPositive (float x) { 221 | return (int)x; 222 | } 223 | 224 | /** Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats from 225 | * -(2^14) to (Float.MAX_VALUE - 2^14). */ 226 | static public int ceil (float x) { 227 | return (int)(x + BIG_ENOUGH_CEIL) - BIG_ENOUGH_INT; 228 | } 229 | 230 | /** Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats that 231 | * are positive. */ 232 | static public int ceilPositive (float x) { 233 | return (int)(x + CEIL); 234 | } 235 | 236 | /** Returns the closest integer to the specified float. This method will only properly round floats from -(2^14) to 237 | * (Float.MAX_VALUE - 2^14). */ 238 | static public int round (float x) { 239 | return (int)(x + BIG_ENOUGH_ROUND) - BIG_ENOUGH_INT; 240 | } 241 | 242 | /** Returns the closest integer to the specified float. This method will only properly round floats that are positive. */ 243 | static public int roundPositive (float x) { 244 | return (int)(x + 0.5f); 245 | } 246 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/Matrix3.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | 3 | /******************************************************************************* 4 | * Copyright 2011 See AUTHORS file. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ******************************************************************************/ 18 | /** from libgdx: https://github.com/libgdx/libgdx */ 19 | import java.io.Serializable; 20 | 21 | /** A 3x3 column major matrix; useful for 2D transforms. 22 | * 23 | * @author mzechner */ 24 | public class Matrix3 implements Serializable { 25 | private static final long serialVersionUID = 7907569533774959788L; 26 | private final static float DEGREE_TO_RAD = (float)Math.PI / 180; 27 | public static final int M00 = 0; 28 | public static final int M01 = 3; 29 | public static final int M02 = 6; 30 | public static final int M10 = 1; 31 | public static final int M11 = 4; 32 | public static final int M12 = 7; 33 | public static final int M20 = 2; 34 | public static final int M21 = 5; 35 | public static final int M22 = 8; 36 | public float[] val = new float[9]; 37 | private float[] tmp = new float[9]; 38 | 39 | public Matrix3 () { 40 | idt(); 41 | } 42 | 43 | public Matrix3 (Matrix3 matrix) { 44 | set(matrix); 45 | } 46 | 47 | /** Sets this matrix to the identity matrix 48 | * @return This matrix for the purpose of chaining operations. */ 49 | public Matrix3 idt () { 50 | val[M00] = 1; 51 | val[M10] = 0; 52 | val[M20] = 0; 53 | val[M01] = 0; 54 | val[M11] = 1; 55 | val[M21] = 0; 56 | val[M02] = 0; 57 | val[M12] = 0; 58 | val[M22] = 1; 59 | return this; 60 | } 61 | 62 | /** Multiplies this matrix with the provided matrix and stores the result in this matrix. For example: 63 | * 64 | *

 65 | 	 * A.mul(B) results in A := AB
 66 | 	 * 
67 | * @param m Matrix to multiply by. 68 | * @return This matrix for the purpose of chaining operations together. */ 69 | public Matrix3 mul (Matrix3 m) { 70 | float v00 = val[M00] * m.val[M00] + val[M01] * m.val[M10] + val[M02] * m.val[M20]; 71 | float v01 = val[M00] * m.val[M01] + val[M01] * m.val[M11] + val[M02] * m.val[M21]; 72 | float v02 = val[M00] * m.val[M02] + val[M01] * m.val[M12] + val[M02] * m.val[M22]; 73 | 74 | float v10 = val[M10] * m.val[M00] + val[M11] * m.val[M10] + val[M12] * m.val[M20]; 75 | float v11 = val[M10] * m.val[M01] + val[M11] * m.val[M11] + val[M12] * m.val[M21]; 76 | float v12 = val[M10] * m.val[M02] + val[M11] * m.val[M12] + val[M12] * m.val[M22]; 77 | 78 | float v20 = val[M20] * m.val[M00] + val[M21] * m.val[M10] + val[M22] * m.val[M20]; 79 | float v21 = val[M20] * m.val[M01] + val[M21] * m.val[M11] + val[M22] * m.val[M21]; 80 | float v22 = val[M20] * m.val[M02] + val[M21] * m.val[M12] + val[M22] * m.val[M22]; 81 | 82 | val[M00] = v00; 83 | val[M10] = v10; 84 | val[M20] = v20; 85 | val[M01] = v01; 86 | val[M11] = v11; 87 | val[M21] = v21; 88 | val[M02] = v02; 89 | val[M12] = v12; 90 | val[M22] = v22; 91 | 92 | return this; 93 | } 94 | 95 | /** Sets this matrix to a rotation matrix that will rotate any vector in counter-clockwise order around the z-axis. 96 | * @param degrees the angle in degrees. 97 | * @return This matrix for the purpose of chaining operations. */ 98 | public Matrix3 setToRotation (float degrees) { 99 | float angle = DEGREE_TO_RAD * degrees; 100 | float cos = (float)Math.cos(angle); 101 | float sin = (float)Math.sin(angle); 102 | 103 | this.val[M00] = cos; 104 | this.val[M10] = sin; 105 | this.val[M20] = 0; 106 | 107 | this.val[M01] = -sin; 108 | this.val[M11] = cos; 109 | this.val[M21] = 0; 110 | 111 | this.val[M02] = 0; 112 | this.val[M12] = 0; 113 | this.val[M22] = 1; 114 | 115 | return this; 116 | } 117 | 118 | /** Sets this matrix to a translation matrix. 119 | * @param x the translation in x 120 | * @param y the translation in y 121 | * @return This matrix for the purpose of chaining operations. */ 122 | public Matrix3 setToTranslation (float x, float y) { 123 | this.val[M00] = 1; 124 | this.val[M10] = 0; 125 | this.val[M20] = 0; 126 | 127 | this.val[M01] = 0; 128 | this.val[M11] = 1; 129 | this.val[M21] = 0; 130 | 131 | this.val[M02] = x; 132 | this.val[M12] = y; 133 | this.val[M22] = 1; 134 | 135 | return this; 136 | } 137 | 138 | /** Sets this matrix to a translation matrix. 139 | * @param translation The translation vector. 140 | * @return This matrix for the purpose of chaining operations. */ 141 | public Matrix3 setToTranslation (Vector2 translation) { 142 | this.val[M00] = 1; 143 | this.val[M10] = 0; 144 | this.val[M20] = 0; 145 | 146 | this.val[M01] = 0; 147 | this.val[M11] = 1; 148 | this.val[M21] = 0; 149 | 150 | this.val[M02] = translation.x; 151 | this.val[M12] = translation.y; 152 | this.val[M22] = 1; 153 | 154 | return this; 155 | } 156 | 157 | /** Sets this matrix to a scaling matrix. 158 | * 159 | * @param scaleX the scale in x 160 | * @param scaleY the scale in y 161 | * @return This matrix for the purpose of chaining operations. */ 162 | public Matrix3 setToScaling (float scaleX, float scaleY) { 163 | val[M00] = scaleX; 164 | val[M10] = 0; 165 | val[M20] = 0; 166 | val[M01] = 0; 167 | val[M11] = scaleY; 168 | val[M21] = 0; 169 | val[M02] = 0; 170 | val[M12] = 0; 171 | val[M22] = 1; 172 | return this; 173 | } 174 | 175 | public String toString () { 176 | return "[" + val[0] + "|" + val[3] + "|" + val[6] + "]\n" + "[" + val[1] + "|" + val[4] + "|" + val[7] + "]\n" + "[" 177 | + val[2] + "|" + val[5] + "|" + val[8] + "]"; 178 | } 179 | 180 | /** @return The determinant of this matrix */ 181 | public float det () { 182 | return val[M00] * val[M11] * val[M22] + val[M01] * val[M12] * val[M20] + val[M02] * val[M10] * val[M21] - val[M00] 183 | * val[M12] * val[M21] - val[M01] * val[M10] * val[M22] - val[M02] * val[M11] * val[M20]; 184 | } 185 | 186 | /** Inverts this matrix given that the determinant is != 0. 187 | * @return This matrix for the purpose of chaining operations. */ 188 | public Matrix3 inv () { 189 | float det = det(); 190 | if (det == 0) throw new Error("Can't invert a singular matrix"); // changed to error from GdxRuntimeException 191 | 192 | float inv_det = 1.0f / det; 193 | 194 | tmp[M00] = val[M11] * val[M22] - val[M21] * val[M12]; 195 | tmp[M10] = val[M20] * val[M12] - val[M10] * val[M22]; 196 | tmp[M20] = val[M10] * val[M21] - val[M20] * val[M11]; 197 | tmp[M01] = val[M21] * val[M02] - val[M01] * val[M22]; 198 | tmp[M11] = val[M00] * val[M22] - val[M20] * val[M02]; 199 | tmp[M21] = val[M20] * val[M01] - val[M00] * val[M21]; 200 | tmp[M02] = val[M01] * val[M12] - val[M11] * val[M02]; 201 | tmp[M12] = val[M10] * val[M02] - val[M00] * val[M12]; 202 | tmp[M22] = val[M00] * val[M11] - val[M10] * val[M01]; 203 | 204 | val[M00] = inv_det * tmp[M00]; 205 | val[M10] = inv_det * tmp[M10]; 206 | val[M20] = inv_det * tmp[M20]; 207 | val[M01] = inv_det * tmp[M01]; 208 | val[M11] = inv_det * tmp[M11]; 209 | val[M21] = inv_det * tmp[M21]; 210 | val[M02] = inv_det * tmp[M02]; 211 | val[M12] = inv_det * tmp[M12]; 212 | val[M22] = inv_det * tmp[M22]; 213 | 214 | return this; 215 | } 216 | 217 | /** Copies the values from the provided matrix to this matrix. 218 | * @param mat The matrix to copy. 219 | * @return This matrix for the purposes of chaining. */ 220 | public Matrix3 set (Matrix3 mat) { 221 | System.arraycopy(mat.val, 0, val, 0, val.length); 222 | return this; 223 | } 224 | 225 | /** Sets this 3x3 matrix to the top left 3x3 corner of the provided 4x4 matrix. 226 | * @param mat The matrix whose top left corner will be copied. This matrix will not be modified. 227 | * @return This matrix for the purpose of chaining operations. */ 228 | public Matrix3 set (Matrix4 mat) { 229 | val[M00] = mat.val[Matrix4.M00]; 230 | val[M10] = mat.val[Matrix4.M10]; 231 | val[M20] = mat.val[Matrix4.M20]; 232 | val[M01] = mat.val[Matrix4.M01]; 233 | val[M11] = mat.val[Matrix4.M11]; 234 | val[M21] = mat.val[Matrix4.M21]; 235 | val[M02] = mat.val[Matrix4.M02]; 236 | val[M12] = mat.val[Matrix4.M12]; 237 | val[M22] = mat.val[Matrix4.M22]; 238 | return this; 239 | } 240 | 241 | /** Adds a translational component to the matrix in the 3rd column. The other columns are untouched. 242 | * @param vector The translation vector. 243 | * @return This matrix for the purpose of chaining. */ 244 | public Matrix3 trn (Vector2 vector) { 245 | val[M02] += vector.x; 246 | val[M12] += vector.y; 247 | return this; 248 | } 249 | 250 | /** Adds a translational component to the matrix in the 3rd column. The other columns are untouched. 251 | * @param x The x-component of the translation vector. 252 | * @param y The y-component of the translation vector. 253 | * @return This matrix for the purpose of chaining. */ 254 | public Matrix3 trn (float x, float y) { 255 | val[M02] += x; 256 | val[M12] += y; 257 | return this; 258 | } 259 | 260 | /** Adds a translational component to the matrix in the 3rd column. The other columns are untouched. 261 | * @param vector The translation vector. (The z-component of the vector is ignored because this is a 3x3 matrix) 262 | * @return This matrix for the purpose of chaining. */ 263 | public Matrix3 trn (Vector3 vector) { 264 | val[M02] += vector.x; 265 | val[M12] += vector.y; 266 | return this; 267 | } 268 | 269 | /** Postmultiplies this matrix by a translation matrix. Postmultiplication is also used by OpenGL ES' 1.x 270 | * glTranslate/glRotate/glScale. 271 | * @param x The x-component of the translation vector. 272 | * @param y The y-component of the translation vector. 273 | * @return This matrix for the purpose of chaining. */ 274 | public Matrix3 translate (float x, float y) { 275 | tmp[M00] = 1; 276 | tmp[M10] = 0; 277 | tmp[M20] = 0; 278 | 279 | tmp[M01] = 0; 280 | tmp[M11] = 1; 281 | tmp[M21] = 0; 282 | 283 | tmp[M02] = x; 284 | tmp[M12] = y; 285 | tmp[M22] = 1; 286 | mul(val, tmp); 287 | return this; 288 | } 289 | 290 | /** Postmultiplies this matrix by a translation matrix. Postmultiplication is also used by OpenGL ES' 1.x 291 | * glTranslate/glRotate/glScale. 292 | * @param translation The translation vector. 293 | * @return This matrix for the purpose of chaining. */ 294 | public Matrix3 translate (Vector2 translation) { 295 | tmp[M00] = 1; 296 | tmp[M10] = 0; 297 | tmp[M20] = 0; 298 | 299 | tmp[M01] = 0; 300 | tmp[M11] = 1; 301 | tmp[M21] = 0; 302 | 303 | tmp[M02] = translation.x; 304 | tmp[M12] = translation.y; 305 | tmp[M22] = 1; 306 | mul(val, tmp); 307 | return this; 308 | } 309 | 310 | /** Postmultiplies this matrix with a (counter-clockwise) rotation matrix. Postmultiplication is also used by OpenGL ES' 1.x 311 | * glTranslate/glRotate/glScale. 312 | * @param angle The angle in degrees 313 | * @return This matrix for the purpose of chaining. */ 314 | public Matrix3 rotate (float angle) { 315 | if (angle == 0) return this; 316 | angle = DEGREE_TO_RAD * angle; 317 | float cos = (float)Math.cos(angle); 318 | float sin = (float)Math.sin(angle); 319 | 320 | tmp[M00] = cos; 321 | tmp[M10] = sin; 322 | tmp[M20] = 0; 323 | 324 | tmp[M01] = -sin; 325 | tmp[M11] = cos; 326 | tmp[M21] = 0; 327 | 328 | tmp[M02] = 0; 329 | tmp[M12] = 0; 330 | tmp[M22] = 1; 331 | mul(val, tmp); 332 | return this; 333 | } 334 | 335 | /** Postmultiplies this matrix with a scale matrix. Postmultiplication is also used by OpenGL ES' 1.x 336 | * glTranslate/glRotate/glScale. 337 | * @param scaleX The scale in the x-axis. 338 | * @param scaleY The scale in the y-axis. 339 | * @return This matrix for the purpose of chaining. */ 340 | public Matrix3 scale (float scaleX, float scaleY) { 341 | tmp[M00] = scaleX; 342 | tmp[M10] = 0; 343 | tmp[M20] = 0; 344 | tmp[M01] = 0; 345 | tmp[M11] = scaleY; 346 | tmp[M21] = 0; 347 | tmp[M02] = 0; 348 | tmp[M12] = 0; 349 | tmp[M22] = 1; 350 | mul(val, tmp); 351 | return this; 352 | } 353 | 354 | /** Postmultiplies this matrix with a scale matrix. Postmultiplication is also used by OpenGL ES' 1.x 355 | * glTranslate/glRotate/glScale. 356 | * @param scale The vector to scale the matrix by. 357 | * @return This matrix for the purpose of chaining. */ 358 | public Matrix3 scale (Vector2 scale) { 359 | tmp[M00] = scale.x; 360 | tmp[M10] = 0; 361 | tmp[M20] = 0; 362 | tmp[M01] = 0; 363 | tmp[M11] = scale.y; 364 | tmp[M21] = 0; 365 | tmp[M02] = 0; 366 | tmp[M12] = 0; 367 | tmp[M22] = 1; 368 | mul(val, tmp); 369 | return this; 370 | } 371 | 372 | /** Get the values in this matrix. 373 | * @return The float values that make up this matrix in column-major order. */ 374 | public float[] getValues () { 375 | return val; 376 | } 377 | 378 | /** Scale the matrix in the both the x and y components by the scalar value. 379 | * @param scale The single value that will be used to scale both the x and y components. 380 | * @return This matrix for the purpose of chaining methods together. */ 381 | public Matrix3 scl (float scale) { 382 | val[M00] *= scale; 383 | val[M11] *= scale; 384 | return this; 385 | } 386 | 387 | /** Scale this matrix using the x and y components of the vector but leave the rest of the matrix alone. 388 | * @param scale The {@link Vector3} to use to scale this matrix. 389 | * @return This matrix for the purpose of chaining methods together. */ 390 | public Matrix3 scl (Vector2 scale) { 391 | val[M00] *= scale.x; 392 | val[M11] *= scale.y; 393 | return this; 394 | } 395 | 396 | /** Scale this matrix using the x and y components of the vector but leave the rest of the matrix alone. 397 | * @param scale The {@link Vector3} to use to scale this matrix. The z component will be ignored. 398 | * @return This matrix for the purpose of chaining methods together. */ 399 | public Matrix3 scl (Vector3 scale) { 400 | val[M00] *= scale.x; 401 | val[M11] *= scale.y; 402 | return this; 403 | } 404 | 405 | /** Transposes the current matrix. 406 | * @return This matrix for the purpose of chaining methods together. */ 407 | public Matrix3 transpose () { 408 | // Where MXY you do not have to change MXX 409 | float v01 = val[M10]; 410 | float v02 = val[M20]; 411 | float v10 = val[M01]; 412 | float v12 = val[M21]; 413 | float v20 = val[M02]; 414 | float v21 = val[M12]; 415 | val[M01] = v01; 416 | val[M02] = v02; 417 | val[M10] = v10; 418 | val[M12] = v12; 419 | val[M20] = v20; 420 | val[M21] = v21; 421 | return this; 422 | } 423 | 424 | /** Multiplies matrix a with matrix b in the following manner: 425 | * 426 | *
427 | 	 * mul(A, B) => A := AB
428 | 	 * 
429 | * @param mata The float array representing the first matrix. Must have at least 9 elements. 430 | * @param matb The float array representing the second matrix. Must have at least 9 elements. */ 431 | private static void mul (float[] mata, float[] matb) { 432 | float v00 = mata[M00] * matb[M00] + mata[M01] * matb[M10] + mata[M02] * matb[M20]; 433 | float v01 = mata[M00] * matb[M01] + mata[M01] * matb[M11] + mata[M02] * matb[M21]; 434 | float v02 = mata[M00] * matb[M02] + mata[M01] * matb[M12] + mata[M02] * matb[M22]; 435 | 436 | float v10 = mata[M10] * matb[M00] + mata[M11] * matb[M10] + mata[M12] * matb[M20]; 437 | float v11 = mata[M10] * matb[M01] + mata[M11] * matb[M11] + mata[M12] * matb[M21]; 438 | float v12 = mata[M10] * matb[M02] + mata[M11] * matb[M12] + mata[M12] * matb[M22]; 439 | 440 | float v20 = mata[M20] * matb[M00] + mata[M21] * matb[M10] + mata[M22] * matb[M20]; 441 | float v21 = mata[M20] * matb[M01] + mata[M21] * matb[M11] + mata[M22] * matb[M21]; 442 | float v22 = mata[M20] * matb[M02] + mata[M21] * matb[M12] + mata[M22] * matb[M22]; 443 | 444 | mata[M00] = v00; 445 | mata[M10] = v10; 446 | mata[M20] = v20; 447 | mata[M01] = v01; 448 | mata[M11] = v11; 449 | mata[M21] = v21; 450 | mata[M02] = v02; 451 | mata[M12] = v12; 452 | mata[M22] = v22; 453 | } 454 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/NumberUtils.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | 3 | /******************************************************************************* 4 | * Copyright 2011 See AUTHORS file. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ******************************************************************************/ 18 | /** from libgdx: https://github.com/libgdx/libgdx */ 19 | 20 | public class NumberUtils { 21 | public static int floatToIntBits (float value) { 22 | return Float.floatToIntBits(value); 23 | } 24 | 25 | public static int floatToRawIntBits (float value) { 26 | return Float.floatToRawIntBits(value); 27 | } 28 | 29 | public static int floatToIntColor (float value) { 30 | return Float.floatToRawIntBits(value); 31 | } 32 | 33 | /** Encodes the ABGR int color as a float. The high bits are masked to avoid using floats in the NaN range, which unfortunately 34 | * means the full range of alpha cannot be used. See {@link Float#intBitsToFloat(int)} javadocs. */ 35 | public static float intToFloatColor (int value) { 36 | return Float.intBitsToFloat(value & 0xfeffffff); 37 | } 38 | 39 | public static float intBitsToFloat (int value) { 40 | return Float.intBitsToFloat(value); 41 | } 42 | 43 | public static long doubleToLongBits (double value) { 44 | return Double.doubleToLongBits(value); 45 | } 46 | 47 | public static double longBitsToDouble (long value) { 48 | return Double.longBitsToDouble(value); 49 | } 50 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/Quaternion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Libgdx by Mario Zechner (badlogicgames@gmail.com) 3 | * 4 | * Libgdx is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Libgdx is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with libgdx. If not, see . 16 | */ 17 | package com.adamratana.rotationvectorcompass.math; 18 | /******************************************************************************* 19 | * Copyright 2011 See AUTHORS file. 20 | * 21 | * Licensed under the Apache License, Version 2.0 (the "License"); 22 | * you may not use this file except in compliance with the License. 23 | * You may obtain a copy of the License at 24 | * 25 | * http://www.apache.org/licenses/LICENSE-2.0 26 | * 27 | * Unless required by applicable law or agreed to in writing, software 28 | * distributed under the License is distributed on an "AS IS" BASIS, 29 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30 | * See the License for the specific language governing permissions and 31 | * limitations under the License. 32 | ******************************************************************************/ 33 | 34 | import java.io.Serializable; 35 | 36 | /** A simple quaternion class. See http://en.wikipedia.org/wiki/Quaternion for more information. 37 | * 38 | * @author badlogicgames@gmail.com 39 | * @author vesuvio */ 40 | public class Quaternion implements Serializable { 41 | private static final long serialVersionUID = -7661875440774897168L; 42 | private static final float NORMALIZATION_TOLERANCE = 0.00001f; 43 | private static Quaternion tmp1 = new Quaternion(0, 0, 0, 0); 44 | private static Quaternion tmp2 = new Quaternion(0, 0, 0, 0); 45 | 46 | public float x; 47 | public float y; 48 | public float z; 49 | public float w; 50 | 51 | /** Constructor, sets the four components of the quaternion. 52 | * @param x The x-component 53 | * @param y The y-component 54 | * @param z The z-component 55 | * @param w The w-component */ 56 | public Quaternion (float x, float y, float z, float w) { 57 | this.set(x, y, z, w); 58 | } 59 | 60 | public Quaternion () { 61 | idt(); 62 | } 63 | 64 | /** Constructor, sets the quaternion components from the given quaternion. 65 | * 66 | * @param quaternion The quaternion to copy. */ 67 | public Quaternion (Quaternion quaternion) { 68 | this.set(quaternion); 69 | } 70 | 71 | /** Constructor, sets the quaternion from the given axis vector and the angle around that axis in degrees. 72 | * 73 | * @param axis The axis 74 | * @param angle The angle in degrees. */ 75 | public Quaternion (Vector3 axis, float angle) { 76 | this.set(axis, angle); 77 | } 78 | 79 | /** Sets the components of the quaternion 80 | * @param x The x-component 81 | * @param y The y-component 82 | * @param z The z-component 83 | * @param w The w-component 84 | * @return This quaternion for chaining */ 85 | public Quaternion set (float x, float y, float z, float w) { 86 | this.x = x; 87 | this.y = y; 88 | this.z = z; 89 | this.w = w; 90 | return this; 91 | } 92 | 93 | /** Sets the quaternion components from the given quaternion. 94 | * @param quaternion The quaternion. 95 | * @return This quaternion for chaining. */ 96 | public Quaternion set (Quaternion quaternion) { 97 | return this.set(quaternion.x, quaternion.y, quaternion.z, quaternion.w); 98 | } 99 | 100 | /** Sets the quaternion components from the given axis and angle around that axis. 101 | * 102 | * @param axis The axis 103 | * @param angle The angle in degrees 104 | * @return This quaternion for chaining. */ 105 | public Quaternion set (Vector3 axis, float angle) { 106 | float l_ang = (float)Math.toRadians(angle); 107 | float l_sin = (float)Math.sin(l_ang / 2); 108 | float l_cos = (float)Math.cos(l_ang / 2); 109 | return this.set(axis.x * l_sin, axis.y * l_sin, axis.z * l_sin, l_cos).nor(); 110 | } 111 | 112 | /** @return a copy of this quaternion */ 113 | public Quaternion cpy () { 114 | return new Quaternion(this); 115 | } 116 | 117 | /** @return the euclidian length of this quaternion */ 118 | public float len () { 119 | return (float)Math.sqrt(x * x + y * y + z * z + w * w); 120 | } 121 | 122 | /** {@inheritDoc} */ 123 | public String toString () { 124 | return "[" + x + "|" + y + "|" + z + "|" + w + "]"; 125 | } 126 | 127 | /** Sets the quaternion to the given euler angles. 128 | * @param yaw the yaw in degrees 129 | * @param pitch the pitch in degress 130 | * @param roll the roll in degess 131 | * @return this quaternion */ 132 | public Quaternion setEulerAngles (float yaw, float pitch, float roll) { 133 | yaw = (float)Math.toRadians(yaw); 134 | pitch = (float)Math.toRadians(pitch); 135 | roll = (float)Math.toRadians(roll); 136 | float num9 = roll * 0.5f; 137 | float num6 = (float)Math.sin(num9); 138 | float num5 = (float)Math.cos(num9); 139 | float num8 = pitch * 0.5f; 140 | float num4 = (float)Math.sin(num8); 141 | float num3 = (float)Math.cos(num8); 142 | float num7 = yaw * 0.5f; 143 | float num2 = (float)Math.sin(num7); 144 | float num = (float)Math.cos(num7); 145 | float f1 = num * num4; 146 | float f2 = num2 * num3; 147 | float f3 = num * num3; 148 | float f4 = num2 * num4; 149 | 150 | x = (f1 * num5) + (f2 * num6); 151 | y = (f2 * num5) - (f1 * num6); 152 | z = (f3 * num6) - (f4 * num5); 153 | w = (f3 * num5) + (f4 * num6); 154 | return this; 155 | } 156 | 157 | /** @return the length of this quaternion without square root */ 158 | public float len2 () { 159 | return x * x + y * y + z * z + w * w; 160 | } 161 | 162 | /** Normalizes this quaternion to unit length 163 | * @return the quaternion for chaining */ 164 | public Quaternion nor () { 165 | float len = len2(); 166 | if (len != 0.f && (Math.abs(len - 1.0f) > NORMALIZATION_TOLERANCE)) { 167 | len = (float)Math.sqrt(len); 168 | w /= len; 169 | x /= len; 170 | y /= len; 171 | z /= len; 172 | } 173 | return this; 174 | } 175 | 176 | /** Conjugate the quaternion. 177 | * 178 | * @return This quaternion for chaining */ 179 | public Quaternion conjugate () { 180 | x = -x; 181 | y = -y; 182 | z = -z; 183 | return this; 184 | } 185 | 186 | // TODO : this would better fit into the vector3 class 187 | /** Transforms the given vector using this quaternion 188 | * 189 | * @param v Vector to transform */ 190 | public void transform (Vector3 v) { 191 | tmp2.set(this); 192 | tmp2.conjugate(); 193 | tmp2.mulLeft(tmp1.set(v.x, v.y, v.z, 0)).mulLeft(this); 194 | 195 | v.x = tmp2.x; 196 | v.y = tmp2.y; 197 | v.z = tmp2.z; 198 | } 199 | 200 | /** Multiplies this quaternion with another one 201 | * 202 | * @param q Quaternion to multiply with 203 | * @return This quaternion for chaining */ 204 | public Quaternion mul (Quaternion q) { 205 | float newX = w * q.x + x * q.w + y * q.z - z * q.y; 206 | float newY = w * q.y + y * q.w + z * q.x - x * q.z; 207 | float newZ = w * q.z + z * q.w + x * q.y - y * q.x; 208 | float newW = w * q.w - x * q.x - y * q.y - z * q.z; 209 | x = newX; 210 | y = newY; 211 | z = newZ; 212 | w = newW; 213 | return this; 214 | } 215 | 216 | /** Multiplies this quaternion with another one in the form of q * this 217 | * 218 | * @param q Quaternion to multiply with 219 | * @return This quaternion for chaining */ 220 | public Quaternion mulLeft (Quaternion q) { 221 | float newX = q.w * x + q.x * w + q.y * z - q.z * y; 222 | float newY = q.w * y + q.y * w + q.z * x - q.x * z; 223 | float newZ = q.w * z + q.z * w + q.x * y - q.y * x; 224 | float newW = q.w * w - q.x * x - q.y * y - q.z * z; 225 | x = newX; 226 | y = newY; 227 | z = newZ; 228 | w = newW; 229 | return this; 230 | } 231 | 232 | // TODO : the matrix4 set(quaternion) doesnt set the last row+col of the matrix to 0,0,0,1 so... that's why there is this 233 | // method 234 | /** Fills a 4x4 matrix with the rotation matrix represented by this quaternion. 235 | * 236 | * @param matrix Matrix to fill */ 237 | public void toMatrix (float[] matrix) { 238 | float xx = x * x; 239 | float xy = x * y; 240 | float xz = x * z; 241 | float xw = x * w; 242 | float yy = y * y; 243 | float yz = y * z; 244 | float yw = y * w; 245 | float zz = z * z; 246 | float zw = z * w; 247 | // Set matrix from quaternion 248 | matrix[Matrix4.M00] = 1 - 2 * (yy + zz); 249 | matrix[Matrix4.M01] = 2 * (xy - zw); 250 | matrix[Matrix4.M02] = 2 * (xz + yw); 251 | matrix[Matrix4.M03] = 0; 252 | matrix[Matrix4.M10] = 2 * (xy + zw); 253 | matrix[Matrix4.M11] = 1 - 2 * (xx + zz); 254 | matrix[Matrix4.M12] = 2 * (yz - xw); 255 | matrix[Matrix4.M13] = 0; 256 | matrix[Matrix4.M20] = 2 * (xz - yw); 257 | matrix[Matrix4.M21] = 2 * (yz + xw); 258 | matrix[Matrix4.M22] = 1 - 2 * (xx + yy); 259 | matrix[Matrix4.M23] = 0; 260 | matrix[Matrix4.M30] = 0; 261 | matrix[Matrix4.M31] = 0; 262 | matrix[Matrix4.M32] = 0; 263 | matrix[Matrix4.M33] = 1; 264 | } 265 | 266 | /** Sets the quaternion to an identity Quaternion 267 | * @return this quaternion for chaining */ 268 | public Quaternion idt () { 269 | this.set(0, 0, 0, 1); 270 | return this; 271 | } 272 | 273 | // todo : the setFromAxis(v3,float) method should replace the set(v3,float) method 274 | /** Sets the quaternion components from the given axis and angle around that axis. 275 | * 276 | * @param axis The axis 277 | * @param angle The angle in degrees 278 | * @return This quaternion for chaining. */ 279 | public Quaternion setFromAxis (Vector3 axis, float angle) { 280 | return setFromAxis(axis.x, axis.y, axis.z, angle); 281 | } 282 | 283 | /** Sets the quaternion components from the given axis and angle around that axis. 284 | * 285 | * @param x X direction of the axis 286 | * @param y Y direction of the axis 287 | * @param z Z direction of the axis 288 | * @param angle The angle in degrees 289 | * @return This quaternion for chaining. */ 290 | public Quaternion setFromAxis (float x, float y, float z, float angle) { 291 | float l_ang = angle * MathUtils.degreesToRadians; 292 | float l_sin = MathUtils.sin(l_ang / 2); 293 | float l_cos = MathUtils.cos(l_ang / 2); 294 | return this.set(x * l_sin, y * l_sin, z * l_sin, l_cos).nor(); 295 | } 296 | 297 | // fromRotationMatrix(xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y, zAxis.y, 298 | // xAxis.z, yAxis.z, zAxis.z); 299 | 300 | // final float m00, final float m01, final float m02, final float m10, 301 | // final float m11, final float m12, final float m20, final float m21, final float m22 302 | 303 | public Quaternion setFromMatrix (Matrix4 matrix) { 304 | return setFromAxes(matrix.val[Matrix4.M00], matrix.val[Matrix4.M01], matrix.val[Matrix4.M02], matrix.val[Matrix4.M10], 305 | matrix.val[Matrix4.M11], matrix.val[Matrix4.M12], matrix.val[Matrix4.M20], matrix.val[Matrix4.M21], 306 | matrix.val[Matrix4.M22]); 307 | } 308 | 309 | /**

310 | * Sets the Quaternion from the given x-, y- and z-axis which have to be orthonormal. 311 | *

312 | * 313 | *

314 | * Taken from Bones framework for JPCT, see http://www.aptalkarga.com/bones/ which in turn took it from Graphics Gem code at 315 | * ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z. 316 | *

317 | * 318 | * @param xx x-axis x-coordinate 319 | * @param xy x-axis y-coordinate 320 | * @param xz x-axis z-coordinate 321 | * @param yx y-axis x-coordinate 322 | * @param yy y-axis y-coordinate 323 | * @param yz y-axis z-coordinate 324 | * @param zx z-axis x-coordinate 325 | * @param zy z-axis y-coordinate 326 | * @param zz z-axis z-coordinate */ 327 | public Quaternion setFromAxes (float xx, float xy, float xz, float yx, float yy, float yz, float zx, float zy, float zz) { 328 | // the trace is the sum of the diagonal elements; see 329 | // http://mathworld.wolfram.com/MatrixTrace.html 330 | final float m00 = xx, m01 = xy, m02 = xz; 331 | final float m10 = yx, m11 = yy, m12 = yz; 332 | final float m20 = zx, m21 = zy, m22 = zz; 333 | final float t = m00 + m11 + m22; 334 | 335 | // we protect the division by s by ensuring that s>=1 336 | double x, y, z, w; 337 | if (t >= 0) { // |w| >= .5 338 | double s = Math.sqrt(t + 1); // |s|>=1 ... 339 | w = 0.5 * s; 340 | s = 0.5 / s; // so this division isn't bad 341 | x = (m21 - m12) * s; 342 | y = (m02 - m20) * s; 343 | z = (m10 - m01) * s; 344 | } else if ((m00 > m11) && (m00 > m22)) { 345 | double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1 346 | x = s * 0.5; // |x| >= .5 347 | s = 0.5 / s; 348 | y = (m10 + m01) * s; 349 | z = (m02 + m20) * s; 350 | w = (m21 - m12) * s; 351 | } else if (m11 > m22) { 352 | double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1 353 | y = s * 0.5; // |y| >= .5 354 | s = 0.5 / s; 355 | x = (m10 + m01) * s; 356 | z = (m21 + m12) * s; 357 | w = (m02 - m20) * s; 358 | } else { 359 | double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1 360 | z = s * 0.5; // |z| >= .5 361 | s = 0.5 / s; 362 | x = (m02 + m20) * s; 363 | y = (m21 + m12) * s; 364 | w = (m10 - m01) * s; 365 | } 366 | 367 | return set((float)x, (float)y, (float)z, (float)w); 368 | } 369 | 370 | /** Set this quaternion to the rotation between two vectors. 371 | * @param v1 The base vector 372 | * @param v2 The target vector 373 | * @return This quaternion for chaining */ 374 | public Quaternion setFromCross (final Vector3 v1, final Vector3 v2) { 375 | final float dot = MathUtils.clamp(Vector3.tmp.set(v1).nor().dot(Vector3.tmp2.set(v2).nor()), -1f, 1f); 376 | return setFromAxis(Vector3.tmp.crs(Vector3.tmp2), (float)Math.acos(dot)); 377 | } 378 | 379 | /** Set this quaternion to the rotation between two vectors. 380 | * @param x1 The base vectors x value 381 | * @param y1 The base vectors y value 382 | * @param z1 The base vectors z value 383 | * @param x2 The target vector x value 384 | * @param y2 The target vector y value 385 | * @param z2 The target vector z value 386 | * @return This quaternion for chaining */ 387 | public Quaternion setFromCross (final float x1, final float y1, final float z1, final float x2, final float y2, final float z2) { 388 | final float dot = MathUtils.clamp(Vector3.tmp.set(x1, y1, z1).nor().dot(Vector3.tmp2.set(x2, y2, z2).nor()), -1f, 1f); 389 | return setFromAxis(Vector3.tmp.crs(Vector3.tmp2), (float)Math.acos(dot)); 390 | } 391 | 392 | /** Spherical linear interpolation between this quaternion and the other quaternion, based on the alpha value in the range 393 | * [0,1]. Taken from. Taken from Bones framework for JPCT, see http://www.aptalkarga.com/bones/ 394 | * @param end the end quaternion 395 | * @param alpha alpha in the range [0,1] 396 | * @return this quaternion for chaining */ 397 | public Quaternion slerp (Quaternion end, float alpha) { 398 | if (this.equals(end)) { 399 | return this; 400 | } 401 | 402 | float result = dot(end); 403 | 404 | if (result < 0.0) { 405 | // Negate the second quaternion and the result of the dot product 406 | end.mul(-1); 407 | result = -result; 408 | } 409 | 410 | // Set the first and second scale for the interpolation 411 | float scale0 = 1 - alpha; 412 | float scale1 = alpha; 413 | 414 | // Check if the angle between the 2 quaternions was big enough to 415 | // warrant such calculations 416 | if ((1 - result) > 0.1) {// Get the angle between the 2 quaternions, 417 | // and then store the sin() of that angle 418 | final double theta = Math.acos(result); 419 | final double invSinTheta = 1f / Math.sin(theta); 420 | 421 | // Calculate the scale for q1 and q2, according to the angle and 422 | // it's sine value 423 | scale0 = (float)(Math.sin((1 - alpha) * theta) * invSinTheta); 424 | scale1 = (float)(Math.sin((alpha * theta)) * invSinTheta); 425 | } 426 | 427 | // Calculate the x, y, z and w values for the quaternion by using a 428 | // special form of linear interpolation for quaternions. 429 | final float x = (scale0 * this.x) + (scale1 * end.x); 430 | final float y = (scale0 * this.y) + (scale1 * end.y); 431 | final float z = (scale0 * this.z) + (scale1 * end.z); 432 | final float w = (scale0 * this.w) + (scale1 * end.w); 433 | set(x, y, z, w); 434 | 435 | // Return the interpolated quaternion 436 | return this; 437 | } 438 | 439 | public boolean equals (final Object o) { 440 | if (this == o) { 441 | return true; 442 | } 443 | if (!(o instanceof Quaternion)) { 444 | return false; 445 | } 446 | final Quaternion comp = (Quaternion)o; 447 | return this.x == comp.x && this.y == comp.y && this.z == comp.z && this.w == comp.w; 448 | 449 | } 450 | 451 | /** Dot product between this and the other quaternion. 452 | * @param other the other quaternion. 453 | * @return this quaternion for chaining. */ 454 | public float dot (Quaternion other) { 455 | return x * other.x + y * other.y + z * other.z + w * other.w; 456 | } 457 | 458 | /** Multiplies the components of this quaternion with the given scalar. 459 | * @param scalar the scalar. 460 | * @return this quaternion for chaining. */ 461 | public Quaternion mul (float scalar) { 462 | this.x *= scalar; 463 | this.y *= scalar; 464 | this.z *= scalar; 465 | this.w *= scalar; 466 | return this; 467 | } 468 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/Util.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | 3 | import android.util.FloatMath; 4 | 5 | public class Util { 6 | /** 7 | * Converts a double to a value between 0 and 360 8 | * 9 | * @param x 10 | * @return double in 0-360 range 11 | */ 12 | public static float floatrev(double x) { 13 | return (float) (x - Math.floor(x / 360.0f) * 360.0f); 14 | } 15 | 16 | /** 17 | * Derive distance between 3d vector a,b 18 | * 19 | * @param a 20 | * @param b 21 | * @return 22 | */ 23 | public static float calcDistance(Vector3 a, Vector3 b) { 24 | return FloatMath.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z)); 25 | } 26 | 27 | /** 28 | * calculates the encompassing circle radius for sides a,b,c of a triangle 29 | * 30 | * @param a 31 | * @param b 32 | * @param c 33 | * @return 34 | */ 35 | public static float calcRadius(float a, float b, float c) { 36 | return (a * b * c) / FloatMath.sqrt((a + b + c) * (a - b + c) * (a + b - c) * (b + c - a)); 37 | } 38 | 39 | /** 40 | * Calculates the angle A given length a and circle radius r, according to 41 | * the law of sines ([a/sin(A) = 2R], thus [A = arcsin(a/2r)]) 42 | * 43 | * @param a 44 | * @param r 45 | * @return angle A in radians 46 | */ 47 | public static float calcAngle(float a, float r) { 48 | return (float) Math.asin(a / (2 * r)); 49 | } 50 | 51 | /** 52 | * Calculates the angle A given length a and circle radius r, according to 53 | * the law of sines ([a/sin(A) = 2R], thus [A = arcsin(a/2r)]) 54 | * 55 | * @param a 56 | * @param r 57 | * @return angle A in radians 58 | */ 59 | public static float calcAngleClamp(float a, float r) { 60 | return (float) Math.asin(Math.min(1, Math.max(-1, a / (2 * r)))); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/Vector2.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | 3 | /******************************************************************************* 4 | * Copyright 2011 See AUTHORS file. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | ******************************************************************************/ 18 | 19 | import java.io.Serializable; 20 | 21 | /** Encapsulates a 2D vector. Allows chaining methods by returning a reference to itself 22 | * @author badlogicgames@gmail.com */ 23 | public class Vector2 implements Serializable { 24 | private static final long serialVersionUID = 913902788239530931L; 25 | 26 | /** Static temporary vector. Use with care! Use only when sure other code will not also use this. 27 | * @see #tmp() **/ 28 | public final static Vector2 tmp = new Vector2(), tmp2 = new Vector2(), tmp3 = new Vector2(); 29 | 30 | public final static Vector2 X = new Vector2(1, 0); 31 | public final static Vector2 Y = new Vector2(0, 1); 32 | public final static Vector2 Zero = new Vector2(0, 0); 33 | 34 | /** the x-component of this vector **/ 35 | public float x; 36 | /** the y-component of this vector **/ 37 | public float y; 38 | 39 | /** Constructs a new vector at (0,0) */ 40 | public Vector2 () { 41 | } 42 | 43 | /** Constructs a vector with the given components 44 | * @param x The x-component 45 | * @param y The y-component */ 46 | public Vector2 (float x, float y) { 47 | this.x = x; 48 | this.y = y; 49 | } 50 | 51 | /** Constructs a vector from the given vector 52 | * @param v The vector */ 53 | public Vector2 (Vector2 v) { 54 | set(v); 55 | } 56 | 57 | /** @return a copy of this vector */ 58 | public Vector2 cpy () { 59 | return new Vector2(this); 60 | } 61 | 62 | /** @return The euclidian length */ 63 | public float len () { 64 | return (float)Math.sqrt(x * x + y * y); 65 | } 66 | 67 | /** @return The squared euclidian length */ 68 | public float len2 () { 69 | return x * x + y * y; 70 | } 71 | 72 | /** Sets this vector from the given vector 73 | * @param v The vector 74 | * @return This vector for chaining */ 75 | public Vector2 set (Vector2 v) { 76 | x = v.x; 77 | y = v.y; 78 | return this; 79 | } 80 | 81 | /** Sets the components of this vector 82 | * @param x The x-component 83 | * @param y The y-component 84 | * @return This vector for chaining */ 85 | public Vector2 set (float x, float y) { 86 | this.x = x; 87 | this.y = y; 88 | return this; 89 | } 90 | 91 | /** Substracts the given vector from this vector. 92 | * @param v The vector 93 | * @return This vector for chaining */ 94 | public Vector2 sub (Vector2 v) { 95 | x -= v.x; 96 | y -= v.y; 97 | return this; 98 | } 99 | 100 | /** Normalizes this vector 101 | * @return This vector for chaining */ 102 | public Vector2 nor () { 103 | float len = len(); 104 | if (len != 0) { 105 | x /= len; 106 | y /= len; 107 | } 108 | return this; 109 | } 110 | 111 | /** Adds the given vector to this vector 112 | * @param v The vector 113 | * @return This vector for chaining */ 114 | public Vector2 add (Vector2 v) { 115 | x += v.x; 116 | y += v.y; 117 | return this; 118 | } 119 | 120 | /** Adds the given components to this vector 121 | * @param x The x-component 122 | * @param y The y-component 123 | * @return This vector for chaining */ 124 | public Vector2 add (float x, float y) { 125 | this.x += x; 126 | this.y += y; 127 | return this; 128 | } 129 | 130 | /** @param v The other vector 131 | * @return The dot product between this and the other vector */ 132 | public float dot (Vector2 v) { 133 | return x * v.x + y * v.y; 134 | } 135 | 136 | /** Multiplies this vector by a scalar 137 | * @param scalar The scalar 138 | * @return This vector for chaining */ 139 | public Vector2 mul (float scalar) { 140 | x *= scalar; 141 | y *= scalar; 142 | return this; 143 | } 144 | 145 | /** Multiplies this vector by a scalar 146 | * @return This vector for chaining */ 147 | public Vector2 mul (float x, float y) { 148 | this.x *= x; 149 | this.y *= y; 150 | return this; 151 | } 152 | 153 | public Vector2 div (float value) { 154 | return this.mul(1/value); 155 | } 156 | 157 | public Vector2 div (float vx, float vy) { 158 | return this.mul(1/vx, 1/vy); 159 | } 160 | 161 | public Vector2 div (Vector2 other) { 162 | return this.mul(1/other.x, 1/other.y); 163 | } 164 | 165 | /** @param v The other vector 166 | * @return the distance between this and the other vector */ 167 | public float dst (Vector2 v) { 168 | final float x_d = v.x - x; 169 | final float y_d = v.y - y; 170 | return (float)Math.sqrt(x_d * x_d + y_d * y_d); 171 | } 172 | 173 | /** @param x The x-component of the other vector 174 | * @param y The y-component of the other vector 175 | * @return the distance between this and the other vector */ 176 | public float dst (float x, float y) { 177 | final float x_d = x - this.x; 178 | final float y_d = y - this.y; 179 | return (float)Math.sqrt(x_d * x_d + y_d * y_d); 180 | } 181 | 182 | /** @param v The other vector 183 | * @return the squared distance between this and the other vector */ 184 | public float dst2 (Vector2 v) { 185 | final float x_d = v.x - x; 186 | final float y_d = v.y - y; 187 | return x_d * x_d + y_d * y_d; 188 | } 189 | 190 | /** @param x The x-component of the other vector 191 | * @param y The y-component of the other vector 192 | * @return the squared distance between this and the other vector */ 193 | public float dst2 (float x, float y) { 194 | final float x_d = x - this.x; 195 | final float y_d = y - this.y; 196 | return x_d * x_d + y_d * y_d; 197 | } 198 | 199 | public String toString () { 200 | return "[" + x + ":" + y + "]"; 201 | } 202 | 203 | /** Substracts the other vector from this vector. 204 | * @param x The x-component of the other vector 205 | * @param y The y-component of the other vector 206 | * @return This vector for chaining */ 207 | public Vector2 sub (float x, float y) { 208 | this.x -= x; 209 | this.y -= y; 210 | return this; 211 | } 212 | 213 | /** NEVER EVER SAVE THIS REFERENCE! Do not use this unless you are aware of the side-effects, e.g. other methods might call this 214 | * as well. 215 | * 216 | * @return a temporary copy of this vector. Use with care as this is backed by a single static Vector2 instance. v1.tmp().add( 217 | * v2.tmp() ) will not work! */ 218 | public Vector2 tmp () { 219 | return tmp.set(this); 220 | } 221 | 222 | /** Multiplies this vector by the given matrix 223 | * @param mat the matrix 224 | * @return this vector */ 225 | public Vector2 mul (Matrix3 mat) { 226 | float x = this.x * mat.val[0] + this.y * mat.val[3] + mat.val[6]; 227 | float y = this.x * mat.val[1] + this.y * mat.val[4] + mat.val[7]; 228 | this.x = x; 229 | this.y = y; 230 | return this; 231 | } 232 | 233 | /** Calculates the 2D cross product between this and the given vector. 234 | * @param v the other vector 235 | * @return the cross product */ 236 | public float crs (Vector2 v) { 237 | return this.x * v.y - this.y * v.x; 238 | } 239 | 240 | /** Calculates the 2D cross product between this and the given vector. 241 | * @param x the x-coordinate of the other vector 242 | * @param y the y-coordinate of the other vector 243 | * @return the cross product */ 244 | public float crs (float x, float y) { 245 | return this.x * y - this.y * x; 246 | } 247 | 248 | /** @return the angle in degrees of this vector (point) relative to the x-axis. Angles are counter-clockwise and between 0 and 249 | * 360. */ 250 | public float angle () { 251 | float angle = (float)Math.atan2(y, x) * MathUtils.radiansToDegrees; 252 | if (angle < 0) angle += 360; 253 | return angle; 254 | } 255 | 256 | /** Sets the angle of the vector. 257 | * @param angle The angle to set. */ 258 | public void setAngle (float angle) { 259 | this.set(len(), 0f); 260 | this.rotate(angle); 261 | } 262 | 263 | /** Rotates the Vector2 by the given angle, counter-clockwise. 264 | * @param degrees the angle in degrees */ 265 | public Vector2 rotate (float degrees) { 266 | float rad = degrees * MathUtils.degreesToRadians; 267 | float cos = (float)Math.cos(rad); 268 | float sin = (float)Math.sin(rad); 269 | 270 | float newX = this.x * cos - this.y * sin; 271 | float newY = this.x * sin + this.y * cos; 272 | 273 | this.x = newX; 274 | this.y = newY; 275 | 276 | return this; 277 | } 278 | 279 | /** Linearly interpolates between this vector and the target vector by alpha which is in the range [0,1]. The result is stored 280 | * in this vector. 281 | * 282 | * @param target The target vector 283 | * @param alpha The interpolation coefficient 284 | * @return This vector for chaining. */ 285 | public Vector2 lerp (Vector2 target, float alpha) { 286 | Vector2 r = this.mul(1.0f - alpha); 287 | r.add(target.tmp().mul(alpha)); 288 | return r; 289 | } 290 | 291 | @Override 292 | public int hashCode () { 293 | final int prime = 31; 294 | int result = 1; 295 | result = prime * result + NumberUtils.floatToIntBits(x); 296 | result = prime * result + NumberUtils.floatToIntBits(y); 297 | return result; 298 | } 299 | 300 | @Override 301 | public boolean equals (Object obj) { 302 | if (this == obj) return true; 303 | if (obj == null) return false; 304 | if (getClass() != obj.getClass()) return false; 305 | Vector2 other = (Vector2)obj; 306 | if (NumberUtils.floatToIntBits(x) != NumberUtils.floatToIntBits(other.x)) return false; 307 | if (NumberUtils.floatToIntBits(y) != NumberUtils.floatToIntBits(other.y)) return false; 308 | return true; 309 | } 310 | 311 | /** Compares this vector with the other vector, using the supplied epsilon for fuzzy equality testing. 312 | * @param obj 313 | * @param epsilon 314 | * @return whether the vectors are the same. */ 315 | public boolean epsilonEquals (Vector2 obj, float epsilon) { 316 | if (obj == null) return false; 317 | if (Math.abs(obj.x - x) > epsilon) return false; 318 | if (Math.abs(obj.y - y) > epsilon) return false; 319 | return true; 320 | } 321 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/Vector3.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | 3 | import java.io.Serializable; 4 | 5 | /******************************************************************************* 6 | * Copyright 2011 See AUTHORS file. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | ******************************************************************************/ 20 | 21 | /** Encapsulates a 3D vector. Allows chaining operations by returning a reference to itself in all modification methods. 22 | * @author badlogicgames@gmail.com */ 23 | public class Vector3 implements Serializable { 24 | private static final long serialVersionUID = 3840054589595372522L; 25 | 26 | /** the x-component of this vector **/ 27 | public float x; 28 | /** the x-component of this vector **/ 29 | public float y; 30 | /** the x-component of this vector **/ 31 | public float z; 32 | 33 | /** Static temporary vector. Use with care! Use only when sure other code will not also use this. 34 | * @see #tmp() **/ 35 | public final static Vector3 tmp = new Vector3(); 36 | /** Static temporary vector. Use with care! Use only when sure other code will not also use this. 37 | * @see #tmp() **/ 38 | public final static Vector3 tmp2 = new Vector3(); 39 | /** Static temporary vector. Use with care! Use only when sure other code will not also use this. 40 | * @see #tmp() **/ 41 | public final static Vector3 tmp3 = new Vector3(); 42 | 43 | public final static Vector3 X = new Vector3(1, 0, 0); 44 | public final static Vector3 Y = new Vector3(0, 1, 0); 45 | public final static Vector3 Z = new Vector3(0, 0, 1); 46 | public final static Vector3 Zero = new Vector3(0, 0, 0); 47 | 48 | private final static Matrix4 tmpMat = new Matrix4(); 49 | 50 | /** Constructs a vector at (0,0,0) */ 51 | public Vector3 () { 52 | } 53 | 54 | /** Creates a vector with the given components 55 | * @param x The x-component 56 | * @param y The y-component 57 | * @param z The z-component */ 58 | public Vector3 (float x, float y, float z) { 59 | this.set(x, y, z); 60 | } 61 | 62 | /** Creates a vector from the given vector 63 | * @param vector The vector */ 64 | public Vector3 (Vector3 vector) { 65 | this.set(vector); 66 | } 67 | 68 | /** Creates a vector from the given array. The array must have at least 3 elements. 69 | * 70 | * @param values The array */ 71 | public Vector3 (float[] values) { 72 | this.set(values[0], values[1], values[2]); 73 | } 74 | 75 | /** Sets the vector to the given components 76 | * 77 | * @param x The x-component 78 | * @param y The y-component 79 | * @param z The z-component 80 | * @return this vector for chaining */ 81 | public Vector3 set (float x, float y, float z) { 82 | this.x = x; 83 | this.y = y; 84 | this.z = z; 85 | return this; 86 | } 87 | 88 | /** Sets the components of the given vector 89 | * 90 | * @param vector The vector 91 | * @return This vector for chaining */ 92 | public Vector3 set (Vector3 vector) { 93 | return this.set(vector.x, vector.y, vector.z); 94 | } 95 | 96 | /** Sets the components from the array. The array must have at least 3 elements 97 | * 98 | * @param values The array 99 | * @return this vector for chaining */ 100 | public Vector3 set (float[] values) { 101 | return this.set(values[0], values[1], values[2]); 102 | } 103 | 104 | /** @return a copy of this vector */ 105 | public Vector3 cpy () { 106 | return new Vector3(this); 107 | } 108 | 109 | /** NEVER EVER SAVE THIS REFERENCE! Do not use this unless you are aware of the side-effects, e.g. other methods might call this 110 | * as well. 111 | * 112 | * @return a temporary copy of this vector */ 113 | public Vector3 tmp () { 114 | return tmp.set(this); 115 | } 116 | 117 | /** NEVER EVER SAVE THIS REFERENCE! Do not use this unless you are aware of the side-effects, e.g. other methods might call this 118 | * as well. 119 | * 120 | * @return a temporary copy of this vector */ 121 | public Vector3 tmp2 () { 122 | return tmp2.set(this); 123 | } 124 | 125 | /** NEVER EVER SAVE THIS REFERENCE! Do not use this unless you are aware of the side-effects, e.g. other methods might call this 126 | * as well. 127 | * 128 | * @return a temporary copy of this vector */ 129 | Vector3 tmp3 () { 130 | return tmp3.set(this); 131 | } 132 | 133 | /** Adds the given vector to this vector 134 | * 135 | * @param vector The other vector 136 | * @return This vector for chaining */ 137 | public Vector3 add (Vector3 vector) { 138 | return this.add(vector.x, vector.y, vector.z); 139 | } 140 | 141 | /** Adds the given vector to this component 142 | * @param x The x-component of the other vector 143 | * @param y The y-component of the other vector 144 | * @param z The z-component of the other vector 145 | * @return This vector for chaining. */ 146 | public Vector3 add (float x, float y, float z) { 147 | return this.set(this.x + x, this.y + y, this.z + z); 148 | } 149 | 150 | /** Adds the given value to all three components of the vector. 151 | * 152 | * @param values The value 153 | * @return This vector for chaining */ 154 | public Vector3 add (float values) { 155 | return this.set(this.x + values, this.y + values, this.z + values); 156 | } 157 | 158 | /** Subtracts the given vector from this vector 159 | * @param a_vec The other vector 160 | * @return This vector for chaining */ 161 | public Vector3 sub (Vector3 a_vec) { 162 | return this.sub(a_vec.x, a_vec.y, a_vec.z); 163 | } 164 | 165 | /** Subtracts the other vector from this vector. 166 | * 167 | * @param x The x-component of the other vector 168 | * @param y The y-component of the other vector 169 | * @param z The z-component of the other vector 170 | * @return This vector for chaining */ 171 | public Vector3 sub (float x, float y, float z) { 172 | return this.set(this.x - x, this.y - y, this.z - z); 173 | } 174 | 175 | /** Subtracts the given value from all components of this vector 176 | * 177 | * @param value The value 178 | * @return This vector for chaining */ 179 | public Vector3 sub (float value) { 180 | return this.set(this.x - value, this.y - value, this.z - value); 181 | } 182 | 183 | /** Multiplies all components of this vector by the given value 184 | * 185 | * @param value The value 186 | * @return This vector for chaining */ 187 | public Vector3 mul (float value) { 188 | return this.set(this.x * value, this.y * value, this.z * value); 189 | } 190 | 191 | /** Multiplies all components of this vector by the given vector3's values 192 | * 193 | * @param other The vector3 to multiply by 194 | * @return This vector for chaining */ 195 | public Vector3 mul (Vector3 other) { 196 | return this.mul(other.x, other.y, other.z); 197 | } 198 | 199 | /** Multiplies all components of this vector by the given values 200 | * 201 | * @param vx X value 202 | * @param vy Y value 203 | * @param vz Z value 204 | * @return This vector for chaining */ 205 | public Vector3 mul (float vx, float vy, float vz) { 206 | return this.set(this.x * vx, this.y * vy, this.z * vz); 207 | } 208 | 209 | /** Divides all components of this vector by the given value 210 | * 211 | * @param value The value 212 | * @return This vector for chaining */ 213 | public Vector3 div (float value) { 214 | return this.mul(1/value); 215 | } 216 | 217 | public Vector3 div (float vx, float vy, float vz) { 218 | return this.mul(1/vx, 1/vy, 1/vz); 219 | } 220 | 221 | public Vector3 div (Vector3 other) { 222 | return this.mul(1/other.x, 1/other.y, 1/other.z); 223 | } 224 | 225 | /** @return The euclidian length */ 226 | public float len () { 227 | return (float)Math.sqrt(x * x + y * y + z * z); 228 | } 229 | 230 | /** @return The squared euclidian length */ 231 | public float len2 () { 232 | return x * x + y * y + z * z; 233 | } 234 | 235 | /** @param vector The other vector 236 | * @return Wether this and the other vector are equal */ 237 | public boolean idt (Vector3 vector) { 238 | return x == vector.x && y == vector.y && z == vector.z; 239 | } 240 | 241 | /** @param vector The other vector 242 | * @return The euclidian distance between this and the other vector */ 243 | public float dst (Vector3 vector) { 244 | float a = vector.x - x; 245 | float b = vector.y - y; 246 | float c = vector.z - z; 247 | 248 | a *= a; 249 | b *= b; 250 | c *= c; 251 | 252 | return (float)Math.sqrt(a + b + c); 253 | } 254 | 255 | /** Normalizes this vector to unit length 256 | * 257 | * @return This vector for chaining */ 258 | public Vector3 nor () { 259 | float len = this.len(); 260 | if (len == 0) { 261 | return this; 262 | } else { 263 | return this.div(len); 264 | } 265 | } 266 | 267 | /** @param vector The other vector 268 | * @return The dot product between this and the other vector */ 269 | public float dot (Vector3 vector) { 270 | return x * vector.x + y * vector.y + z * vector.z; 271 | } 272 | 273 | /** Sets this vector to the cross product between it and the other vector. 274 | * @param vector The other vector 275 | * @return This vector for chaining */ 276 | public Vector3 crs (Vector3 vector) { 277 | return this.set(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x); 278 | } 279 | 280 | /** Sets this vector to the cross product between it and the other vector. 281 | * @param x The x-component of the other vector 282 | * @param y The y-component of the other vector 283 | * @param z The z-component of the other vector 284 | * @return This vector for chaining */ 285 | public Vector3 crs (float x, float y, float z) { 286 | return this.set(this.y * z - this.z * y, this.z * x - this.x * z, this.x * y - this.y * x); 287 | } 288 | 289 | /** Multiplies the vector by the given matrix. 290 | * @param matrix The matrix 291 | * @return This vector for chaining */ 292 | public Vector3 mul (Matrix4 matrix) { 293 | float l_mat[] = matrix.val; 294 | return this.set(x * l_mat[Matrix4.M00] + y * l_mat[Matrix4.M01] + z * l_mat[Matrix4.M02] + l_mat[Matrix4.M03], x 295 | * l_mat[Matrix4.M10] + y * l_mat[Matrix4.M11] + z * l_mat[Matrix4.M12] + l_mat[Matrix4.M13], x * l_mat[Matrix4.M20] + y 296 | * l_mat[Matrix4.M21] + z * l_mat[Matrix4.M22] + l_mat[Matrix4.M23]); 297 | } 298 | 299 | /** Multiplies this vector by the given matrix dividing by w. This is mostly used to project/unproject vectors via a perspective 300 | * projection matrix. 301 | * 302 | * @param matrix The matrix. 303 | * @return This vector for chaining */ 304 | public Vector3 prj (Matrix4 matrix) { 305 | float l_mat[] = matrix.val; 306 | float l_w = x * l_mat[Matrix4.M30] + y * l_mat[Matrix4.M31] + z * l_mat[Matrix4.M32] + l_mat[Matrix4.M33]; 307 | return this.set((x * l_mat[Matrix4.M00] + y * l_mat[Matrix4.M01] + z * l_mat[Matrix4.M02] + l_mat[Matrix4.M03]) / l_w, (x 308 | * l_mat[Matrix4.M10] + y * l_mat[Matrix4.M11] + z * l_mat[Matrix4.M12] + l_mat[Matrix4.M13]) 309 | / l_w, (x * l_mat[Matrix4.M20] + y * l_mat[Matrix4.M21] + z * l_mat[Matrix4.M22] + l_mat[Matrix4.M23]) / l_w); 310 | } 311 | 312 | /** Multiplies this vector by the first three columns of the matrix, essentially only applying rotation and scaling. 313 | * 314 | * @param matrix The matrix 315 | * @return This vector for chaining */ 316 | public Vector3 rot (Matrix4 matrix) { 317 | float l_mat[] = matrix.val; 318 | return this.set(x * l_mat[Matrix4.M00] + y * l_mat[Matrix4.M01] + z * l_mat[Matrix4.M02], x * l_mat[Matrix4.M10] + y 319 | * l_mat[Matrix4.M11] + z * l_mat[Matrix4.M12], x * l_mat[Matrix4.M20] + y * l_mat[Matrix4.M21] + z * l_mat[Matrix4.M22]); 320 | } 321 | 322 | /** Rotates this vector by the given angle around the given axis. 323 | * 324 | * @param axisX the x-component of the axis 325 | * @param axisY the y-component of the axis 326 | * @param axisZ the z-component of the axis 327 | * @return This vector for chaining */ 328 | public Vector3 rotate (float angle, float axisX, float axisY, float axisZ) { 329 | return rotate(tmp.set(axisX, axisY, axisZ), angle); 330 | } 331 | 332 | /** Rotates this vector by the given angle around the given axis. 333 | * 334 | * @param axis 335 | * @param angle the angle 336 | * @return This vector for chaining */ 337 | public Vector3 rotate (Vector3 axis, float angle) { 338 | tmpMat.setToRotation(axis, angle); 339 | return this.mul(tmpMat); 340 | } 341 | 342 | /** @return Whether this vector is a unit length vector */ 343 | public boolean isUnit () { 344 | return this.len() == 1; 345 | } 346 | 347 | /** @return Whether this vector is a zero vector */ 348 | public boolean isZero () { 349 | return x == 0 && y == 0 && z == 0; 350 | } 351 | 352 | /** Linearly interpolates between this vector and the target vector by alpha which is in the range [0,1]. The result is stored 353 | * in this vector. 354 | * 355 | * @param target The target vector 356 | * @param alpha The interpolation coefficient 357 | * @return This vector for chaining. */ 358 | public Vector3 lerp (Vector3 target, float alpha) { 359 | Vector3 r = this.mul(1.0f - alpha); 360 | r.add(target.tmp().mul(alpha)); 361 | return r; 362 | } 363 | 364 | /** Spherically interpolates between this vector and the target vector by alpha which is in the range [0,1]. The result is 365 | * stored in this vector. 366 | * 367 | * @param target The target vector 368 | * @param alpha The interpolation coefficient 369 | * @return This vector for chaining. */ 370 | public Vector3 slerp (Vector3 target, float alpha) { 371 | float dot = dot(target); 372 | if (dot > 0.99995 || dot < 0.9995) { 373 | this.add(target.tmp().sub(this).mul(alpha)); 374 | this.nor(); 375 | return this; 376 | } 377 | 378 | if (dot > 1) dot = 1; 379 | if (dot < -1) dot = -1; 380 | 381 | float theta0 = (float)Math.acos(dot); 382 | float theta = theta0 * alpha; 383 | Vector3 v2 = target.tmp().sub(x * dot, y * dot, z * dot); 384 | v2.nor(); 385 | return this.mul((float)Math.cos(theta)).add(v2.mul((float)Math.sin(theta))).nor(); 386 | } 387 | 388 | /** {@inheritDoc} */ 389 | public String toString () { 390 | return x + "," + y + "," + z; 391 | } 392 | 393 | /** Returns the dot product between this and the given vector. 394 | * 395 | * @param x The x-component of the other vector 396 | * @param y The y-component of the other vector 397 | * @param z The z-component of the other vector 398 | * @return The dot product */ 399 | public float dot (float x, float y, float z) { 400 | return this.x * x + this.y * y + this.z * z; 401 | } 402 | 403 | /** Returns the squared distance between this point and the given point 404 | * 405 | * @param point The other point 406 | * @return The squared distance */ 407 | public float dst2 (Vector3 point) { 408 | 409 | float a = point.x - x; 410 | float b = point.y - y; 411 | float c = point.z - z; 412 | 413 | a *= a; 414 | b *= b; 415 | c *= c; 416 | 417 | return a + b + c; 418 | } 419 | 420 | /** Returns the squared distance between this point and the given point 421 | * 422 | * @param x The x-component of the other point 423 | * @param y The y-component of the other point 424 | * @param z The z-component of the other point 425 | * @return The squared distance */ 426 | public float dst2 (float x, float y, float z) { 427 | float a = x - this.x; 428 | float b = y - this.y; 429 | float c = z - this.z; 430 | 431 | a *= a; 432 | b *= b; 433 | c *= c; 434 | 435 | return a + b + c; 436 | } 437 | 438 | public float dst (float x, float y, float z) { 439 | return (float)Math.sqrt(dst2(x, y, z)); 440 | } 441 | 442 | /** {@inheritDoc} */ 443 | @Override 444 | public int hashCode () { 445 | final int prime = 31; 446 | int result = 1; 447 | result = prime * result + NumberUtils.floatToIntBits(x); 448 | result = prime * result + NumberUtils.floatToIntBits(y); 449 | result = prime * result + NumberUtils.floatToIntBits(z); 450 | return result; 451 | } 452 | 453 | /** {@inheritDoc} */ 454 | @Override 455 | public boolean equals (Object obj) { 456 | if (this == obj) return true; 457 | if (obj == null) return false; 458 | if (getClass() != obj.getClass()) return false; 459 | Vector3 other = (Vector3)obj; 460 | if (NumberUtils.floatToIntBits(x) != NumberUtils.floatToIntBits(other.x)) return false; 461 | if (NumberUtils.floatToIntBits(y) != NumberUtils.floatToIntBits(other.y)) return false; 462 | if (NumberUtils.floatToIntBits(z) != NumberUtils.floatToIntBits(other.z)) return false; 463 | return true; 464 | } 465 | 466 | /** Compares this vector with the other vector, using the supplied epsilon for fuzzy equality testing. 467 | * @param obj 468 | * @param epsilon 469 | * @return whether the vectors are the same. */ 470 | public boolean epsilonEquals (Vector3 obj, float epsilon) { 471 | if (obj == null) return false; 472 | if (Math.abs(obj.x - x) > epsilon) return false; 473 | if (Math.abs(obj.y - y) > epsilon) return false; 474 | if (Math.abs(obj.z - z) > epsilon) return false; 475 | return true; 476 | } 477 | 478 | /** Compares this vector with the other vector, using the supplied epsilon for fuzzy equality testing. 479 | * @return whether the vectors are the same. */ 480 | public boolean epsilonEquals (float x, float y, float z, float epsilon) { 481 | if (Math.abs(x - this.x) > epsilon) return false; 482 | if (Math.abs(y - this.y) > epsilon) return false; 483 | if (Math.abs(z - this.z) > epsilon) return false; 484 | return true; 485 | } 486 | 487 | /** Scales the vector components by the given scalars. 488 | * 489 | * @param scalarX 490 | * @param scalarY 491 | * @param scalarZ */ 492 | public Vector3 scale (float scalarX, float scalarY, float scalarZ) { 493 | x *= scalarX; 494 | y *= scalarY; 495 | z *= scalarZ; 496 | return this; 497 | } 498 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/math/Vector4.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.math; 2 | 3 | /** 4 | * Derived from Vector3 from libgdx - encapsulates a 4 component vector for 5 | * representing Homogeneous Coordinates 6 | */ 7 | public final class Vector4 { 8 | /** the x-component of this vector **/ 9 | public float x; 10 | /** the x-component of this vector **/ 11 | public float y; 12 | /** the x-component of this vector **/ 13 | public float z; 14 | 15 | public float w; 16 | 17 | private static Vector4 tmp = new Vector4(); 18 | private static Vector4 tmp2 = new Vector4(); 19 | private static Vector4 tmp3 = new Vector4(); 20 | 21 | /** 22 | * Multiplies the vector by the given matrix. 23 | * 24 | * @param matrix 25 | * The matrix 26 | * @return This vector for chaining 27 | */ 28 | public Vector4 mul(Matrix4 matrix) { 29 | float l_mat[] = matrix.val; 30 | return this.set(x * l_mat[Matrix4.M00] + y * l_mat[Matrix4.M01] + z * l_mat[Matrix4.M02] + w * l_mat[Matrix4.M03], x * l_mat[Matrix4.M10] + y * l_mat[Matrix4.M11] + z * l_mat[Matrix4.M12] + w * l_mat[Matrix4.M13], x * l_mat[Matrix4.M20] + y 31 | * l_mat[Matrix4.M21] + z * l_mat[Matrix4.M22] + w * l_mat[Matrix4.M23], x * l_mat[Matrix4.M30] + y * l_mat[Matrix4.M31] + z * l_mat[Matrix4.M32] + w * l_mat[Matrix4.M33]); 32 | } 33 | 34 | /** 35 | * Constructs a vector at (0,0,0) 36 | */ 37 | public Vector4() { 38 | } 39 | 40 | /** 41 | * Creates a vector with the given components 42 | * 43 | * @param x 44 | * The x-component 45 | * @param y 46 | * The y-component 47 | * @param z 48 | * The z-component 49 | */ 50 | public Vector4(float x, float y, float z, float w) { 51 | this.set(x, y, z, w); 52 | } 53 | 54 | /** 55 | * Creates a vector from the given vector 56 | * 57 | * @param vector 58 | * The vector 59 | */ 60 | public Vector4(Vector4 vector) { 61 | this.set(vector); 62 | } 63 | 64 | /** 65 | * Creates a vector from the given array. The array must have at least 3 66 | * elements. 67 | * 68 | * @param values 69 | * The array 70 | */ 71 | public Vector4(float[] values) { 72 | this.set(values[0], values[1], values[2], values[3]); 73 | } 74 | 75 | /** 76 | * Sets the vector to the given components 77 | * 78 | * @param x 79 | * The x-component 80 | * @param y 81 | * The y-component 82 | * @param z 83 | * The z-component 84 | * @return this vector for chaining 85 | */ 86 | public Vector4 set(float x, float y, float z, float w) { 87 | this.x = x; 88 | this.y = y; 89 | this.z = z; 90 | this.w = w; 91 | return this; 92 | } 93 | 94 | /** 95 | * Sets the components of the given vector 96 | * 97 | * @param vector 98 | * The vector 99 | * @return This vector for chaining 100 | */ 101 | public Vector4 set(Vector4 vector) { 102 | return this.set(vector.x, vector.y, vector.z, vector.w); 103 | } 104 | 105 | /** 106 | * Sets the components from the array. The array must have at least 3 107 | * elements 108 | * 109 | * @param values 110 | * The array 111 | * @return this vector for chaining 112 | */ 113 | public Vector4 set(float[] values) { 114 | return this.set(values[0], values[1], values[2], values[3]); 115 | } 116 | 117 | /** 118 | * @return a copy of this vector 119 | */ 120 | public Vector4 cpy() { 121 | return new Vector4(this); 122 | } 123 | 124 | /** 125 | * NEVER EVER SAVE THIS REFERENCE! 126 | * 127 | * @return 128 | */ 129 | public Vector4 tmp() { 130 | return tmp.set(this); 131 | } 132 | 133 | /** 134 | * NEVER EVER SAVE THIS REFERENCE! 135 | * 136 | * @return 137 | */ 138 | public Vector4 tmp2() { 139 | return tmp2.set(this); 140 | } 141 | 142 | /** 143 | * NEVER EVER SAVE THIS REFERENCE! 144 | * 145 | * @return 146 | */ 147 | Vector4 tmp3() { 148 | return tmp3.set(this); 149 | } 150 | 151 | /** 152 | * Adds the given vector to this vector 153 | * 154 | * @param vector 155 | * The other vector 156 | * @return This vector for chaining 157 | */ 158 | public Vector4 add(Vector4 vector) { 159 | return this.add(vector.x, vector.y, vector.z, vector.w); 160 | } 161 | 162 | /** 163 | * Adds the given vector to this component 164 | * 165 | * @param x 166 | * The x-component of the other vector 167 | * @param y 168 | * The y-component of the other vector 169 | * @param z 170 | * The z-component of the other vector 171 | * @return This vector for chaining. 172 | */ 173 | public Vector4 add(float x, float y, float z, float w) { 174 | return this.set(this.x + x, this.y + y, this.z + z, this.w + w); 175 | } 176 | 177 | /** 178 | * Adds the given value to all three components of the vector. 179 | * 180 | * @param values 181 | * The value 182 | * @return This vector for chaining 183 | */ 184 | public Vector4 add(float values) { 185 | return this.set(this.x + values, this.y + values, this.z + values, this.w + values); 186 | } 187 | 188 | /** 189 | * Subtracts the given vector from this vector 190 | * 191 | * @param a_vec 192 | * The other vector 193 | * @return This vector for chaining 194 | */ 195 | public Vector4 sub(Vector4 a_vec) { 196 | return this.sub(a_vec.x, a_vec.y, a_vec.z, a_vec.w); 197 | } 198 | 199 | /** 200 | * Subtracts the other vector from this vector. 201 | * 202 | * @param x 203 | * The x-component of the other vector 204 | * @param y 205 | * The y-component of the other vector 206 | * @param z 207 | * The z-component of the other vector 208 | * @return This vector for chaining 209 | */ 210 | public Vector4 sub(float x, float y, float z, float w) { 211 | return this.set(this.x - x, this.y - y, this.z - z, this.w - w); 212 | } 213 | 214 | /** 215 | * Subtracts the given value from all components of this vector 216 | * 217 | * @param value 218 | * The value 219 | * @return This vector for chaining 220 | */ 221 | public Vector4 sub(float value) { 222 | return this.set(this.x - value, this.y - value, this.z - value, this.w - value); 223 | } 224 | 225 | /** 226 | * Multiplies all components of this vector by the given value 227 | * 228 | * @param value 229 | * The value 230 | * @return This vector for chaining 231 | */ 232 | public Vector4 mul(float value) { 233 | return this.set(this.x * value, this.y * value, this.z * value, this.w * value); 234 | } 235 | 236 | /** 237 | * Divides all components of this vector by the given value 238 | * 239 | * @param value 240 | * The value 241 | * @return This vector for chaining 242 | */ 243 | public Vector4 div(float value) { 244 | float d = 1 / value; 245 | return this.set(this.x * d, this.y * d, this.z * d, this.w * d); 246 | } 247 | 248 | /** 249 | * @return The euclidian length 250 | */ 251 | public float len() { 252 | return (float) Math.sqrt(x * x + y * y + z * z + w * w); 253 | } 254 | 255 | /** 256 | * @return The squared euclidian length 257 | */ 258 | public float len2() { 259 | return x * x + y * y + z * z + w * w; 260 | } 261 | 262 | /** 263 | * @param vector 264 | * The other vector 265 | * @return Wether this and the other vector are equal 266 | */ 267 | public boolean idt(Vector4 vector) { 268 | return x == vector.x && y == vector.y && z == vector.z && w == vector.w; 269 | } 270 | 271 | /** 272 | * @param vector 273 | * The other vector 274 | * @return The euclidian distance between this and the other vector 275 | */ 276 | public float dst(Vector4 vector) { 277 | float a = vector.x - x; 278 | float b = vector.y - y; 279 | float c = vector.z - z; 280 | float d = vector.w - w; 281 | a *= a; 282 | b *= b; 283 | c *= c; 284 | d *= d; 285 | 286 | return (float) Math.sqrt(a + b + c + d); 287 | } 288 | 289 | /** 290 | * @param vector 291 | * The other vector 292 | * @return The squared euclidian distance between this and the other vector 293 | */ 294 | public float dist2(Vector4 vector) { 295 | float a = vector.x - x; 296 | float b = vector.y - y; 297 | float c = vector.z - z; 298 | float d = vector.w - w; 299 | return a * a + b * b + c * c + d * d; 300 | } 301 | 302 | /** 303 | * Normalizes this vector to unit length 304 | * 305 | * @return This vector for chaining 306 | */ 307 | public Vector4 nor() { 308 | if (x == 0 && y == 0 && z == 0 && w == 0) 309 | return this; 310 | else 311 | return this.div(this.len()); 312 | } 313 | 314 | /** 315 | * {@inheritDoc} 316 | */ 317 | @Override 318 | public int hashCode() { 319 | final int prime = 31; 320 | int result = 1; 321 | result = prime * result + Float.floatToIntBits(x); 322 | result = prime * result + Float.floatToIntBits(y); 323 | result = prime * result + Float.floatToIntBits(z); 324 | result = prime * result + Float.floatToIntBits(w); 325 | return result; 326 | } 327 | 328 | /** 329 | * {@inheritDoc} 330 | */ 331 | @Override 332 | public boolean equals(Object obj) { 333 | if (this == obj) 334 | return true; 335 | if (obj == null) 336 | return false; 337 | if (getClass() != obj.getClass()) 338 | return false; 339 | Vector4 other = (Vector4) obj; 340 | if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) 341 | return false; 342 | if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) 343 | return false; 344 | if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z)) 345 | return false; 346 | if (Float.floatToIntBits(w) != Float.floatToIntBits(other.w)) 347 | return false; 348 | return true; 349 | } 350 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/rotation/MagAccelListener.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.rotation; 2 | 3 | import android.hardware.Sensor; 4 | import android.hardware.SensorEvent; 5 | import android.hardware.SensorEventListener; 6 | import android.hardware.SensorManager; 7 | 8 | /** 9 | * Magnetometer / Accelerometer sensor fusion Smoothed by means of simple high 10 | * pass filter 11 | * 12 | * When it receives an event it will notify the constructor-specified delegate. 13 | * 14 | * @author Adam 15 | * 16 | */ 17 | public class MagAccelListener implements SensorEventListener { 18 | private float[] mRotationM = new float[16]; 19 | // smoothing factor - tune to taste 20 | private final float mFilterFactor = 0.1f; 21 | private final float mFilterFactorInv = 1.0f - mFilterFactor; 22 | private boolean mIsReady = false; 23 | // smoothed accelerometer values 24 | private float[] mAccelVals = new float[] { 0f, 0f, 9.8f }; 25 | // smoothed magnetometer values 26 | private float[] mMagVals = new float[] { 0.5f, 0f, 0f }; 27 | private RotationUpdateDelegate mRotationUpdateDelegate; 28 | 29 | public MagAccelListener(RotationUpdateDelegate rotationUpdateDelegate) { 30 | mRotationUpdateDelegate = rotationUpdateDelegate; 31 | } 32 | 33 | @Override 34 | public void onSensorChanged(SensorEvent event) { 35 | switch (event.sensor.getType()) { 36 | case Sensor.TYPE_ACCELEROMETER: 37 | smooth(event.values, mAccelVals, mAccelVals); 38 | break; 39 | case Sensor.TYPE_MAGNETIC_FIELD: 40 | smooth(event.values, mMagVals, mMagVals); 41 | mIsReady = true; 42 | break; 43 | default: 44 | break; 45 | } 46 | // wait until we have both a new accelerometer and magnetometer sample 47 | if (mIsReady) { 48 | mIsReady = false; 49 | fuseValues(); 50 | } 51 | } 52 | 53 | private void fuseValues() { 54 | SensorManager.getRotationMatrix(mRotationM, null, mAccelVals, mMagVals); 55 | mRotationUpdateDelegate.onRotationUpdate(mRotationM); 56 | } 57 | 58 | private void smooth(float[] inv, float prevv[], float outv[]) { 59 | outv[0] = inv[0] * mFilterFactor + prevv[0] * (mFilterFactorInv); 60 | outv[1] = inv[1] * mFilterFactor + prevv[1] * (mFilterFactorInv); 61 | outv[2] = inv[2] * mFilterFactor + prevv[2] * (mFilterFactorInv); 62 | } 63 | 64 | @Override 65 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 66 | } 67 | } -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/rotation/RotationUpdateDelegate.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.rotation; 2 | 3 | /** 4 | * Delegate to receive updates when rotation of device changes 5 | * 6 | * @author Adam 7 | * 8 | */ 9 | public interface RotationUpdateDelegate { 10 | /** 11 | * 12 | * @param newMatrix 13 | * - 4x4 matrix 14 | */ 15 | public void onRotationUpdate(float newMatrix[]); 16 | } 17 | -------------------------------------------------------------------------------- /RotationVectorCompass/src/com/adamratana/rotationvectorcompass/rotation/RotationVectorListener.java: -------------------------------------------------------------------------------- 1 | package com.adamratana.rotationvectorcompass.rotation; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorEvent; 6 | import android.hardware.SensorEventListener; 7 | import android.hardware.SensorManager; 8 | 9 | /** 10 | * Uses the new (Android 2.3+) Rotation Vector virtual sensor, which is a sensor 11 | * fusion of the magnetometer, accelerometer, and gyroscope (if present) Note: 12 | * this does not seem to function correctly for Samsung Galaxy Tab 10.1, in that 13 | * North does not point North 14 | * 15 | * When it receives an event it will notify the constructor-specified delegate. 16 | * 17 | * @author Adam 18 | * 19 | */ 20 | public class RotationVectorListener implements SensorEventListener { 21 | private float[] mRotationM = new float[16]; 22 | private RotationUpdateDelegate mRotationUpdateDelegate; 23 | 24 | public RotationVectorListener(RotationUpdateDelegate rotationUpdateDelegate) { 25 | mRotationUpdateDelegate = rotationUpdateDelegate; 26 | } 27 | 28 | @SuppressLint("NewApi") 29 | @Override 30 | public void onSensorChanged(SensorEvent event) { 31 | SensorManager.getRotationMatrixFromVector(mRotationM, event.values); 32 | mRotationUpdateDelegate.onRotationUpdate(mRotationM); 33 | } 34 | 35 | @Override 36 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 37 | } 38 | } --------------------------------------------------------------------------------