├── .gitignore ├── 742F6502D5230041EB9AF8277CA2BAC7.jpg ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dac │ │ └── zjms │ │ └── com │ │ └── compass │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dac │ │ │ └── zjms │ │ │ └── com │ │ │ └── compass │ │ │ ├── App.java │ │ │ ├── LevelView.java │ │ │ ├── MainActivity.java │ │ │ ├── provider │ │ │ ├── AccelerometerCompassProvider.java │ │ │ ├── ImprovedOrientationSensor1Provider.java │ │ │ ├── ImprovedOrientationSensor2Provider.java │ │ │ └── OrientationProvider.java │ │ │ └── representation │ │ │ ├── Matrix.java │ │ │ ├── MatrixF4x4.java │ │ │ ├── Quaternion.java │ │ │ ├── Vector3f.java │ │ │ └── Vector4f.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── compass.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── imagebutton.png │ │ └── level.png │ │ └── values │ │ ├── attrs_level_view.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── dac │ └── zjms │ └── com │ └── compass │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | /.idea 12 | -------------------------------------------------------------------------------- /742F6502D5230041EB9AF8277CA2BAC7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/742F6502D5230041EB9AF8277CA2BAC7.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 罗盘和水平仪 2 | 3 | 4 | 5 | ![742F6502D5230041EB9AF8277CA2BAC7](https://github.com/wangHwYD/Compass/blob/master/742F6502D5230041EB9AF8277CA2BAC7.jpg) 6 | 7 | 8 | 9 | ## License 10 | 11 | ``` 12 | Copyright 2016 huazhiyuan2008 13 | 14 | Licensed under the Apache License, Version 2.0 (the "License"); 15 | you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "dac.zjms.com.compass" 11 | minSdkVersion 19 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 28 | implementation 'com.android.support:appcompat-v7:28.+' 29 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | 34 | //butterknife 35 | implementation 'com.jakewharton:butterknife:8.8.1' 36 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 37 | 38 | implementation 'com.blankj:utilcode:1.20.3' 39 | } 40 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/dac/zjms/com/compass/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("dac.zjms.com.compass", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/App.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass; 2 | 3 | import android.app.Application; 4 | 5 | import com.blankj.utilcode.util.Utils; 6 | 7 | /** 8 | * 作者:wanghaowen on 2018/11/19 15:07 9 | * 邮箱:13409909996@163.com 10 | * >_< 11 | */ 12 | public class App extends Application { 13 | @Override 14 | public void onCreate() { 15 | super.onCreate(); 16 | Utils.init(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/LevelView.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.PointF; 8 | import android.os.Vibrator; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | 12 | 13 | public class LevelView extends View { 14 | 15 | /** 16 | * 最大圈半径 17 | */ 18 | private float mLimitRadius = 0; 19 | 20 | /** 21 | * 气泡半径 22 | */ 23 | private float mBubbleRadius; 24 | 25 | /** 26 | * 最大限制圈颜色 27 | */ 28 | private int mLimitColor; 29 | 30 | /** 31 | * 限制圈宽度 32 | */ 33 | private float mLimitCircleWidth; 34 | 35 | 36 | /** 37 | * 气泡中心标准圆颜色 38 | */ 39 | private int mBubbleRuleColor; 40 | 41 | /** 42 | * 气泡中心标准圆宽 43 | */ 44 | private float mBubbleRuleWidth; 45 | 46 | /** 47 | * 气泡中心标准圆半径 48 | */ 49 | private float mBubbleRuleRadius; 50 | 51 | /** 52 | * 水平后的颜色 53 | */ 54 | private int mHorizontalColor; 55 | //水平2级 56 | private int mHorizontalColorTWO; 57 | 58 | /** 59 | * 气泡颜色 60 | */ 61 | private int mBubbleColor; 62 | 63 | private Paint mBubblePaint; 64 | private Paint mLimitPaint; 65 | private Paint mBubbleRulePaint; 66 | 67 | /** 68 | * 中心点坐标 69 | */ 70 | private PointF centerPnt = new PointF(); 71 | 72 | /** 73 | * 计算后的气泡点 74 | */ 75 | private PointF bubblePoint; 76 | private double pitchAngle = -90; 77 | private double rollAngle = -90; 78 | private Vibrator vibrator; 79 | 80 | public LevelView(Context context) { 81 | super(context); 82 | init(null, 0); 83 | } 84 | 85 | public LevelView(Context context, AttributeSet attrs) { 86 | super(context, attrs); 87 | init(attrs, 0); 88 | } 89 | 90 | public LevelView(Context context, AttributeSet attrs, int defStyle) { 91 | super(context, attrs, defStyle); 92 | init(attrs, defStyle); 93 | } 94 | 95 | private void init(AttributeSet attrs, int defStyle) { 96 | // Load attributes 97 | final TypedArray a = getContext().obtainStyledAttributes( 98 | attrs, R.styleable.LevelView, defStyle, 0); 99 | 100 | mBubbleRuleColor = a.getColor(R.styleable.LevelView_bubbleRuleColor, mBubbleRuleColor); 101 | 102 | mBubbleColor = a.getColor(R.styleable.LevelView_bubbleColor, mBubbleColor); 103 | mLimitColor = a.getColor(R.styleable.LevelView_limitColor, mLimitColor); 104 | 105 | mHorizontalColor = a.getColor(R.styleable.LevelView_horizontalColor, mHorizontalColor); 106 | mHorizontalColorTWO = a.getColor(R.styleable.LevelView_horizontalColor_two, mHorizontalColorTWO); 107 | 108 | 109 | mLimitRadius = a.getDimension(R.styleable.LevelView_limitRadius, mLimitRadius); 110 | mBubbleRadius = a.getDimension(R.styleable.LevelView_bubbleRadius, mBubbleRadius); 111 | mLimitCircleWidth = a.getDimension(R.styleable.LevelView_limitCircleWidth, mLimitCircleWidth); 112 | 113 | mBubbleRuleWidth = a.getDimension(R.styleable.LevelView_bubbleRuleWidth, mBubbleRuleWidth); 114 | 115 | mBubbleRuleRadius = a.getDimension(R.styleable.LevelView_bubbleRuleRadius, mBubbleRuleRadius); 116 | 117 | 118 | a.recycle(); 119 | 120 | 121 | mBubblePaint = new Paint(); 122 | 123 | mBubblePaint.setColor(mBubbleColor); 124 | mBubblePaint.setStyle(Paint.Style.FILL); 125 | mBubblePaint.setAntiAlias(true); 126 | 127 | mLimitPaint = new Paint(); 128 | 129 | mLimitPaint.setStyle(Paint.Style.STROKE); 130 | mLimitPaint.setColor(mLimitColor); 131 | mLimitPaint.setStrokeWidth(mLimitCircleWidth); 132 | //抗锯齿 133 | mLimitPaint.setAntiAlias(true); 134 | 135 | mBubbleRulePaint = new Paint(); 136 | mBubbleRulePaint.setColor(mBubbleRuleColor); 137 | mBubbleRulePaint.setStyle(Paint.Style.STROKE); 138 | mBubbleRulePaint.setStrokeWidth(mBubbleRuleWidth); 139 | mBubbleRulePaint.setAntiAlias(true); 140 | 141 | vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); 142 | 143 | } 144 | 145 | @Override 146 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 147 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 148 | 149 | calculateCenter(widthMeasureSpec, heightMeasureSpec); 150 | } 151 | 152 | private void calculateCenter(int widthMeasureSpec, int heightMeasureSpec) { 153 | int width = MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.UNSPECIFIED); 154 | 155 | int height = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.UNSPECIFIED); 156 | 157 | int center = Math.min(width, height) / 2; 158 | 159 | centerPnt.set(center, center); 160 | } 161 | 162 | 163 | @Override 164 | protected void onDraw(Canvas canvas) { 165 | super.onDraw(canvas); 166 | boolean isCenter = isCenter(bubblePoint); 167 | int bubbleColor; 168 | if (isCenter){ 169 | bubbleColor = mHorizontalColor; 170 | //水平时振动 171 | vibrator.vibrate(10); 172 | 173 | }else { 174 | boolean center21 = isCenter2(bubblePoint); 175 | // int bubbleColorTwo = center21 ? mHorizontalColorTWO : mBubbleColor; 176 | if (center21){ 177 | bubbleColor = mHorizontalColorTWO ; 178 | }else { 179 | bubbleColor = mBubbleColor ; 180 | } 181 | 182 | 183 | } 184 | 185 | int limitCircleColor = isCenter ? mHorizontalColor : mLimitColor; 186 | // int bubbleColor = isCenter ? mHorizontalColor : mBubbleColor; 187 | 188 | 189 | 190 | 191 | mBubblePaint.setColor(bubbleColor); 192 | mLimitPaint.setColor(limitCircleColor); 193 | 194 | 195 | canvas.drawCircle(centerPnt.x, centerPnt.y, mBubbleRuleRadius, mBubbleRulePaint); 196 | canvas.drawCircle(centerPnt.x, centerPnt.y, mLimitRadius, mLimitPaint); 197 | 198 | drawBubble(canvas); 199 | 200 | } 201 | 202 | private boolean isCenter(PointF bubblePoint){ 203 | 204 | if(bubblePoint == null){ 205 | return false; 206 | } 207 | 208 | // return Math.abs(bubblePoint.x - centerPnt.x) < 1 && Math.abs(bubblePoint.y - centerPnt.y) < 1; 209 | return Math.abs(bubblePoint.x - centerPnt.x) < 15 && Math.abs(bubblePoint.y - centerPnt.y) < 15; 210 | } 211 | private boolean isCenter2(PointF bubblePoint){ 212 | 213 | if(bubblePoint == null){ 214 | return false; 215 | } 216 | 217 | // return Math.abs(bubblePoint.x - centerPnt.x) < 1 && Math.abs(bubblePoint.y - centerPnt.y) < 1; 218 | return Math.abs(bubblePoint.x - centerPnt.x) < 50 && Math.abs(bubblePoint.y - centerPnt.y) < 50; 219 | } 220 | private void drawBubble(Canvas canvas) { 221 | if(bubblePoint != null){ 222 | canvas.drawCircle(bubblePoint.x, bubblePoint.y, mBubbleRadius, mBubblePaint); 223 | } 224 | } 225 | 226 | /** 227 | * Convert angle to screen coordinate point. 228 | * @param rollAngle 横滚角(弧度) 229 | * @param pitchAngle 俯仰角(弧度) 230 | * @return 231 | */ 232 | private PointF convertCoordinate(double rollAngle, double pitchAngle, double radius){ 233 | double scale = radius / Math.toRadians(90); 234 | 235 | //以圆心为原点,使用弧度表示坐标 236 | double x0 = -(rollAngle * scale); 237 | double y0 = -(pitchAngle * scale); 238 | 239 | //使用屏幕坐标表示气泡点 240 | double x = centerPnt.x - x0; 241 | double y = centerPnt.y - y0; 242 | 243 | return new PointF((float)x, (float)y); 244 | } 245 | 246 | /** 247 | * 248 | * @param pitchAngle (弧度) 249 | * @param rollAngle (弧度) 250 | */ 251 | @SuppressWarnings("ResultOfMethodCallIgnored") 252 | public void setAngle(double rollAngle, double pitchAngle) { 253 | 254 | this.pitchAngle = pitchAngle; 255 | this.rollAngle = rollAngle; 256 | 257 | //考虑气泡边界不超出限制圆,此处减去气泡的显示半径,做为最终的限制圆半径 258 | float limitRadius = mLimitRadius - mBubbleRadius; 259 | 260 | bubblePoint = convertCoordinate(rollAngle, pitchAngle, mLimitRadius); 261 | outLimit(bubblePoint, limitRadius); 262 | 263 | //坐标超出最大圆,取法向圆上的点 264 | if(outLimit(bubblePoint, limitRadius)){ 265 | onCirclePoint(bubblePoint, limitRadius); 266 | } 267 | 268 | invalidate(); 269 | } 270 | 271 | /** 272 | * 验证气泡点是否超过限制{@link #mLimitRadius} 273 | * @param bubblePnt 274 | * @return 275 | */ 276 | private boolean outLimit(PointF bubblePnt, float limitRadius){ 277 | 278 | float cSqrt = (bubblePnt.x - centerPnt.x)*(bubblePnt.x - centerPnt.x) 279 | + (centerPnt.y - bubblePnt.y) * + (centerPnt.y - bubblePnt.y); 280 | 281 | 282 | if(cSqrt - limitRadius * limitRadius > 0){ 283 | return true; 284 | } 285 | return false; 286 | } 287 | 288 | /** 289 | * 计算圆心到 bubblePnt点在圆上的交点坐标 290 | * 即超出圆后的最大圆上坐标 291 | * @param bubblePnt 气泡点 292 | * @param limitRadius 限制圆的半径 293 | * @return 294 | */ 295 | private PointF onCirclePoint(PointF bubblePnt, double limitRadius) { 296 | double azimuth = Math.atan2((bubblePnt.y - centerPnt.y), (bubblePnt.x - centerPnt.x)); 297 | azimuth = azimuth < 0 ? 2 * Math.PI + azimuth : azimuth; 298 | 299 | //圆心+半径+角度 求圆上的坐标 300 | double x1 = centerPnt.x + limitRadius * Math.cos(azimuth); 301 | double y1 = centerPnt.y + limitRadius * Math.sin(azimuth); 302 | 303 | bubblePnt.set((float) x1, (float) y1); 304 | 305 | return bubblePnt; 306 | } 307 | 308 | public double getPitchAngle(){ 309 | return this.pitchAngle; 310 | } 311 | 312 | public double getRollAngle(){ 313 | return this.rollAngle; 314 | } 315 | 316 | 317 | } -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/MainActivity.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.hardware.SensorManager; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | import android.view.animation.Animation; 11 | import android.view.animation.RotateAnimation; 12 | import android.widget.FrameLayout; 13 | import android.widget.ImageView; 14 | import android.widget.Toast; 15 | 16 | import com.blankj.utilcode.util.LogUtils; 17 | 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | import butterknife.Unbinder; 21 | import dac.zjms.com.compass.provider.AccelerometerCompassProvider; 22 | 23 | import static android.widget.Toast.LENGTH_LONG; 24 | 25 | /** 26 | * 作者:wanghaowen on 2018/11/19 11:31 27 | * 邮箱:13409909996@163.com 28 | * >_< 29 | */ 30 | public class MainActivity extends AppCompatActivity { 31 | @BindView(R.id.gv_hv) 32 | LevelView gv_hv; 33 | @BindView(R.id.fl_frame) 34 | FrameLayout fl_frame; 35 | @BindView(R.id.compass) 36 | ImageView compass; 37 | Unbinder mBind; 38 | private AccelerometerCompassProvider currentOrientationProvider; 39 | @Override 40 | protected void onCreate(@Nullable Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | mBind = ButterKnife.bind(this); 44 | SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); 45 | if (sensorManager != null) { 46 | currentOrientationProvider = new AccelerometerCompassProvider(sensorManager); 47 | currentOrientationProvider.start(); 48 | currentOrientationProvider.callback = callback; 49 | mHandler.postDelayed(statusUpdater, time); 50 | compass.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View view) { 53 | stop = !stop; 54 | if (stop) { 55 | currentOrientationProvider.start(); 56 | mHandler.postDelayed(statusUpdater, time); 57 | } else { 58 | currentOrientationProvider.stop(); 59 | } 60 | } 61 | }); 62 | }else { 63 | Toast.makeText(MainActivity.this,"该设备无传感器",LENGTH_LONG).show(); 64 | } 65 | 66 | } 67 | 68 | AccelerometerCompassProvider.Callback callback = new AccelerometerCompassProvider.Callback() { 69 | @SuppressLint("SetTextI18n") 70 | @Override 71 | public void onOrientationChanged(double o, double p, double r) { 72 | 73 | orientation = o; 74 | pi = p; 75 | ro = r; 76 | 77 | LogUtils.e("1.------"+o+"\r\n2.------"+p+"\r\n3.------"+r); 78 | } 79 | }; 80 | boolean stop = true; 81 | // 记录指南针图片转过的角度 82 | float currentDegree = 0f; 83 | 84 | double theFilterValue = 0.02; 85 | 86 | double orientation1; 87 | double orientation; 88 | double pi; 89 | double ro; 90 | long time = 300; 91 | protected final Handler mHandler = new Handler(); 92 | 93 | protected Runnable statusUpdater = new Runnable() { 94 | 95 | @SuppressLint("SetTextI18n") 96 | @Override 97 | public void run() { 98 | if (stop) { 99 | float du = (float) normalizeDegree(Math.toDegrees(orientation)); 100 | if (Math.abs(du) - Math.abs(orientation1) > 1) { 101 | RotateAnimation ra = new RotateAnimation(currentDegree, -du, 102 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 103 | // 设置动画的持续时间 104 | ra.setDuration(200); 105 | // 运行动画 106 | ra.setFillAfter(true); // 设置保持动画最后的状态 107 | compass.startAnimation(ra); 108 | 109 | } 110 | currentDegree = -du; 111 | //水平仪 112 | // gv_hv.setAngle(pi, ro); 113 | gv_hv.setAngle(ro, pi); 114 | mHandler.postDelayed(statusUpdater, time); 115 | } 116 | 117 | } 118 | }; 119 | private double normalizeDegree(double paramFloat) { 120 | return (720.0F + paramFloat) % 360.0F; 121 | } 122 | 123 | @Override 124 | protected void onDestroy() { 125 | super.onDestroy(); 126 | currentOrientationProvider.stop(); 127 | stop = false; 128 | mHandler.removeCallbacks(statusUpdater); 129 | if (mBind != null) { 130 | mBind.unbind(); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/provider/AccelerometerCompassProvider.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.provider; 2 | 3 | import android.hardware.Sensor; 4 | import android.hardware.SensorEvent; 5 | import android.hardware.SensorManager; 6 | 7 | /** 8 | * The orientation provider that delivers the current orientation from the {@link Sensor#TYPE_ACCELEROMETER 9 | * Accelerometer} and {@link Sensor#TYPE_MAGNETIC_FIELD Compass}. 10 | * 11 | * @author Alexander Pacha 12 | */ 13 | public class AccelerometerCompassProvider extends OrientationProvider { 14 | 15 | /** 16 | * Compass values 17 | */ 18 | private float[] magnitudeValues = new float[3]; 19 | 20 | /** 21 | * Accelerometer values 22 | */ 23 | private float[] accelerometerValues = new float[3]; 24 | 25 | /** 26 | * Inclination values 27 | */ 28 | private float[] inclinationValues = new float[16]; 29 | private float[] values = new float[3]; 30 | private float[] R = new float[9]; 31 | 32 | public Callback callback = null; 33 | 34 | /** 35 | * Initialises a new AccelerometerCompassProvider 36 | * 37 | * @param sensorManager The android sensor manager 38 | */ 39 | public AccelerometerCompassProvider(SensorManager sensorManager) { 40 | super(sensorManager); 41 | 42 | //Add the compass and the accelerometer 43 | sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)); 44 | sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)); 45 | } 46 | 47 | @Override 48 | public void onSensorChanged(SensorEvent event) { 49 | 50 | // we received a sensor event. it is a good practice to check 51 | // that we received the proper event 52 | if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { 53 | magnitudeValues = event.values; 54 | 55 | System.arraycopy(event.values, 0, magnitudeValues, 0, magnitudeValues.length); 56 | if (callback != null) { 57 | callback.onMagneticValues(magnitudeValues[0], magnitudeValues[1], magnitudeValues[2]); 58 | } 59 | } else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 60 | 61 | accelerometerValues = event.values; 62 | System.arraycopy(event.values, 0, accelerometerValues, 0, accelerometerValues.length); 63 | if (callback != null) { 64 | callback.onAccelerometerChanged(accelerometerValues[1], accelerometerValues[2], accelerometerValues[0]); 65 | } 66 | } 67 | 68 | 69 | if (magnitudeValues != null && accelerometerValues != null) { 70 | // Fuse accelerometer with compass 71 | 72 | 73 | SensorManager.getRotationMatrix(currentOrientationRotationMatrix.matrix, inclinationValues, accelerometerValues, 74 | magnitudeValues); 75 | // Transform rotation matrix to quaternion 76 | SensorManager.getOrientation(currentOrientationRotationMatrix.matrix, values); 77 | currentOrientationQuaternion.setRowMajor(currentOrientationRotationMatrix.matrix); 78 | if (callback != null) { 79 | callback.onOrientationChanged(values[0], values[1], values[2]); 80 | } 81 | 82 | } 83 | } 84 | 85 | public float[] getValues() { 86 | return values; 87 | } 88 | 89 | public void setValues(float[] values) { 90 | this.values = values; 91 | } 92 | 93 | public float[] getR() { 94 | return R; 95 | } 96 | 97 | public void setR(float[] r) { 98 | R = r; 99 | } 100 | 101 | public float[] getMagnitudeValues() { 102 | return magnitudeValues; 103 | } 104 | 105 | public float[] getAccelerometerValues() { 106 | return accelerometerValues; 107 | } 108 | 109 | public float[] getInclinationValues() { 110 | return inclinationValues; 111 | } 112 | 113 | 114 | public abstract static class Callback { 115 | public void onOrientationChanged(double orientation, double pitch, double roll) { 116 | 117 | } 118 | 119 | public void onAccelerometerChanged(double dx, double dy, double dz) { 120 | 121 | } 122 | 123 | public void onMagneticValues(double dx, double dy, double dz) { 124 | 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/provider/ImprovedOrientationSensor1Provider.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.provider; 2 | 3 | import android.hardware.Sensor; 4 | import android.hardware.SensorEvent; 5 | import android.hardware.SensorManager; 6 | import android.util.Log; 7 | 8 | import dac.zjms.com.compass.representation.Quaternion; 9 | 10 | 11 | /** 12 | * The orientation provider that delivers the absolute orientation from the {@link Sensor#TYPE_GYROSCOPE 13 | * Gyroscope} and {@link Sensor#TYPE_ROTATION_VECTOR Android Rotation Vector sensor}. 14 | * 15 | * It mainly relies on the gyroscope, but corrects with the Android Rotation Vector which also provides an absolute 16 | * estimation of current orientation. The correction is a static weight. 17 | * 18 | * @author Alexander Pacha 19 | * 20 | */ 21 | public class ImprovedOrientationSensor1Provider extends OrientationProvider { 22 | 23 | /** 24 | * Constant specifying the factor between a Nano-second and a second 25 | */ 26 | private static final float NS2S = 1.0f / 1000000000.0f; 27 | 28 | /** 29 | * The quaternion that stores the difference that is obtained by the gyroscope. 30 | * Basically it contains a rotational difference encoded into a quaternion. 31 | * 32 | * To obtain the absolute orientation one must add this into an initial position by 33 | * multiplying it with another quaternion 34 | */ 35 | private final Quaternion deltaQuaternion = new Quaternion(); 36 | 37 | /** 38 | * The Quaternions that contain the current rotation (Angle and axis in Quaternion format) of the Gyroscope 39 | */ 40 | private Quaternion quaternionGyroscope = new Quaternion(); 41 | 42 | /** 43 | * The quaternion that contains the absolute orientation as obtained by the rotationVector sensor. 44 | */ 45 | private Quaternion quaternionRotationVector = new Quaternion(); 46 | 47 | /** 48 | * The time-stamp being used to record the time when the last gyroscope event occurred. 49 | */ 50 | private long timestamp; 51 | 52 | /** 53 | * This is a filter-threshold for discarding Gyroscope measurements that are below a certain level and 54 | * potentially are only noise and not real motion. Values from the gyroscope are usually between 0 (stop) and 55 | * 10 (rapid rotation), so 0.1 seems to be a reasonable threshold to filter noise (usually smaller than 0.1) and 56 | * real motion (usually > 0.1). Note that there is a chance of missing real motion, if the use is turning the 57 | * device really slowly, so this value has to find a balance between accepting noise (threshold = 0) and missing 58 | * slow user-action (threshold > 0.5). 0.1 seems to work fine for most applications. 59 | * 60 | */ 61 | private static final double EPSILON = 0.1f; 62 | 63 | /** 64 | * Value giving the total velocity of the gyroscope (will be high, when the device is moving fast and low when 65 | * the device is standing still). This is usually a value between 0 and 10 for normal motion. Heavy shaking can 66 | * increase it to about 25. Keep in mind, that these values are time-depended, so changing the sampling rate of 67 | * the sensor will affect this value! 68 | */ 69 | private double gyroscopeRotationVelocity = 0; 70 | 71 | /** 72 | * Flag indicating, whether the orientations were initialised from the rotation vector or not. If false, the 73 | * gyroscope can not be used (since it's only meaningful to calculate differences from an initial state). If 74 | * true, 75 | * the gyroscope can be used normally. 76 | */ 77 | private boolean positionInitialised = false; 78 | 79 | /** 80 | * Counter that sums the number of consecutive frames, where the rotationVector and the gyroscope were 81 | * significantly different (and the dot-product was smaller than 0.7). This event can either happen when the 82 | * angles of the rotation vector explode (e.g. during fast tilting) or when the device was shaken heavily and 83 | * the gyroscope is now completely off. 84 | */ 85 | private int panicCounter; 86 | 87 | /** 88 | * This weight determines directly how much the rotation sensor will be used to correct (in 89 | * Sensor-fusion-scenario 1 - SensorSelection.GyroscopeAndRotationVector). Must be a value between 0 and 1. 90 | * 0 means that the system entirely relies on the gyroscope, whereas 1 means that the system relies entirely on 91 | * the rotationVector. 92 | */ 93 | private static final float DIRECT_INTERPOLATION_WEIGHT = 0.005f; 94 | 95 | /** 96 | * The threshold that indicates an outlier of the rotation vector. If the dot-product between the two vectors 97 | * (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1, 98 | * if they are exactly the same) the system falls back to the gyroscope values only and just ignores the 99 | * rotation vector. 100 | * 101 | * This value should be quite high (> 0.7) to filter even the slightest discrepancies that causes jumps when 102 | * tiling the device. Possible values are between 0 and 1, where a value close to 1 means that even a very small 103 | * difference between the two sensors will be treated as outlier, whereas a value close to zero means that the 104 | * almost any discrepancy between the two sensors is tolerated. 105 | */ 106 | private static final float OUTLIER_THRESHOLD = 0.85f; 107 | 108 | /** 109 | * The threshold that indicates a massive discrepancy between the rotation vector and the gyroscope orientation. 110 | * If the dot-product between the two vectors 111 | * (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1, if 112 | * they are exactly the same), the system will start increasing the panic counter (that probably indicates a 113 | * gyroscope failure). 114 | * 115 | * This value should be lower than OUTLIER_THRESHOLD (0.5 - 0.7) to only start increasing the panic counter, 116 | * when there is a 117 | * huge discrepancy between the two fused sensors. 118 | */ 119 | private static final float OUTLIER_PANIC_THRESHOLD = 0.65f; 120 | 121 | /** 122 | * The threshold that indicates that a chaos state has been established rather than just a temporary peak in the 123 | * rotation vector (caused by exploding angled during fast tilting). 124 | * 125 | * If the chaosCounter is bigger than this threshold, the current position will be reset to whatever the 126 | * rotation vector indicates. 127 | */ 128 | private static final int PANIC_THRESHOLD = 60; 129 | 130 | /** 131 | * Some temporary variables to save allocations 132 | */ 133 | public float[] temporaryQuaternion = new float[4]; 134 | final private Quaternion correctedQuaternion = new Quaternion(); 135 | final private Quaternion interpolatedQuaternion = new Quaternion(); 136 | public float mDotProd; 137 | 138 | public float[] getTemporaryQuaternion() { 139 | return temporaryQuaternion; 140 | } 141 | 142 | /** 143 | * Initialises a new ImprovedOrientationSensor1Provider 144 | * 145 | * @param sensorManager The android sensor manager 146 | */ 147 | public ImprovedOrientationSensor1Provider(SensorManager sensorManager) { 148 | super(sensorManager); 149 | 150 | //Add the gyroscope and rotation Vector 151 | sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)); 152 | sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)); 153 | } 154 | 155 | @Override 156 | public void onSensorChanged(SensorEvent event) { 157 | 158 | if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { 159 | // Process rotation vector (just safe it) 160 | // Calculate angle. Starting with API_18, Android will provide this value as event.values[3], but if not, we have to calculate it manually. 161 | SensorManager.getQuaternionFromVector(temporaryQuaternion, event.values); 162 | 163 | // Store in quaternion 164 | quaternionRotationVector.setXYZW(temporaryQuaternion[1], temporaryQuaternion[2], temporaryQuaternion[3], -temporaryQuaternion[0]); 165 | if (!positionInitialised) { 166 | // Override 167 | quaternionGyroscope.set(quaternionRotationVector); 168 | positionInitialised = true; 169 | } 170 | 171 | } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { 172 | // Process Gyroscope and perform fusion 173 | 174 | // This timestep's delta rotation to be multiplied by the current rotation 175 | // after computing it from the gyro sample data. 176 | if (timestamp != 0) { 177 | final float dT = (event.timestamp - timestamp) * NS2S; 178 | // Axis of the rotation sample, not normalized yet. 179 | float axisX = event.values[0]; 180 | float axisY = event.values[1]; 181 | float axisZ = event.values[2]; 182 | 183 | // Calculate the angular speed of the sample 184 | gyroscopeRotationVelocity = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); 185 | 186 | // Normalize the rotation vector if it's big enough to get the axis 187 | if (gyroscopeRotationVelocity > EPSILON) { 188 | axisX /= gyroscopeRotationVelocity; 189 | axisY /= gyroscopeRotationVelocity; 190 | axisZ /= gyroscopeRotationVelocity; 191 | } 192 | 193 | // Integrate around this axis with the angular speed by the timestep 194 | // in order to get a delta rotation from this sample over the timestep 195 | // We will convert this axis-angle representation of the delta rotation 196 | // into a quaternion before turning it into the rotation matrix. 197 | double thetaOverTwo = gyroscopeRotationVelocity * dT / 2.0f; 198 | double sinThetaOverTwo = Math.sin(thetaOverTwo); 199 | double cosThetaOverTwo = Math.cos(thetaOverTwo); 200 | deltaQuaternion.setX((float) (sinThetaOverTwo * axisX)); 201 | deltaQuaternion.setY((float) (sinThetaOverTwo * axisY)); 202 | deltaQuaternion.setZ((float) (sinThetaOverTwo * axisZ)); 203 | deltaQuaternion.setW(-(float) cosThetaOverTwo); 204 | 205 | // Move current gyro orientation 206 | deltaQuaternion.multiplyByQuat(quaternionGyroscope, quaternionGyroscope); 207 | 208 | // Calculate dot-product to calculate whether the two orientation sensors have diverged 209 | // (if the dot-product is closer to 0 than to 1), because it should be close to 1 if both are the same. 210 | mDotProd = quaternionGyroscope.dotProduct(quaternionRotationVector); 211 | 212 | // If they have diverged, rely on gyroscope only (this happens on some devices when the rotation vector "jumps"). 213 | if (Math.abs(mDotProd) < OUTLIER_THRESHOLD) { 214 | // Increase panic counter 215 | if (Math.abs(mDotProd) < OUTLIER_PANIC_THRESHOLD) { 216 | panicCounter++; 217 | } 218 | 219 | // Directly use Gyro 220 | setOrientationQuaternionAndMatrix(quaternionGyroscope); 221 | 222 | } else { 223 | // Both are nearly saying the same. Perform normal fusion. 224 | 225 | // Interpolate with a fixed weight between the two absolute quaternions obtained from gyro and rotation vector sensors 226 | // The weight should be quite low, so the rotation vector corrects the gyro only slowly, and the output keeps responsive. 227 | quaternionGyroscope.slerp(quaternionRotationVector, interpolatedQuaternion, DIRECT_INTERPOLATION_WEIGHT); 228 | 229 | // Use the interpolated value between gyro and rotationVector 230 | setOrientationQuaternionAndMatrix(interpolatedQuaternion); 231 | // Override current gyroscope-orientation 232 | quaternionGyroscope.copyVec4(interpolatedQuaternion); 233 | 234 | // Reset the panic counter because both sensors are saying the same again 235 | panicCounter = 0; 236 | } 237 | 238 | if (panicCounter > PANIC_THRESHOLD) { 239 | Log.d("Rotation Vector", 240 | "Panic counter is bigger than threshold; this indicates a Gyroscope failure. Panic reset is imminent."); 241 | 242 | if (gyroscopeRotationVelocity < 3) { 243 | Log.d("Rotation Vector", 244 | "Performing Panic-reset. Resetting orientation to rotation-vector value."+gyroscopeRotationVelocity); 245 | 246 | // Manually set position to whatever rotation vector says. 247 | setOrientationQuaternionAndMatrix(quaternionRotationVector); 248 | // Override current gyroscope-orientation with corrected value 249 | quaternionGyroscope.copyVec4(quaternionRotationVector); 250 | 251 | panicCounter = 0; 252 | } else { 253 | Log.d("Rotation Vector", 254 | String.format( 255 | "Panic reset delayed due to ongoing motion (user is still shaking the device). Gyroscope Velocity: %.2f > 3", 256 | gyroscopeRotationVelocity)); 257 | } 258 | } 259 | } 260 | timestamp = event.timestamp; 261 | } 262 | } 263 | 264 | /** 265 | * Sets the output quaternion and matrix with the provided quaternion and synchronises the setting 266 | * 267 | * @param quaternion The Quaternion to set (the result of the sensor fusion) 268 | */ 269 | private void setOrientationQuaternionAndMatrix(Quaternion quaternion) { 270 | correctedQuaternion.set(quaternion); 271 | // We inverted w in the deltaQuaternion, because currentOrientationQuaternion required it. 272 | // Before converting it back to matrix representation, we need to revert this process 273 | correctedQuaternion.w(-correctedQuaternion.w()); 274 | 275 | synchronized (synchronizationToken) { 276 | // Use gyro only 277 | currentOrientationQuaternion.copyVec4(quaternion); 278 | 279 | // Set the rotation matrix as well to have both representations 280 | SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix, correctedQuaternion.array()); 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/provider/ImprovedOrientationSensor2Provider.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.provider; 2 | 3 | import android.hardware.Sensor; 4 | import android.hardware.SensorEvent; 5 | import android.hardware.SensorManager; 6 | import android.util.Log; 7 | 8 | import dac.zjms.com.compass.representation.Quaternion; 9 | 10 | 11 | /** 12 | * The orientation provider that delivers the absolute orientation from the {@link Sensor#TYPE_GYROSCOPE 13 | * Gyroscope} and {@link Sensor#TYPE_ROTATION_VECTOR Android Rotation Vector sensor}. 14 | * 15 | * It mainly relies on the gyroscope, but corrects with the Android Rotation Vector which also provides an absolute 16 | * estimation of current orientation. The correction is a static weight. 17 | * 18 | * @author Alexander Pacha 19 | * 20 | */ 21 | public class ImprovedOrientationSensor2Provider extends OrientationProvider { 22 | 23 | /** 24 | * Constant specifying the factor between a Nano-second and a second 25 | */ 26 | private static final float NS2S = 1.0f / 1000000000.0f; 27 | 28 | /** 29 | * The quaternion that stores the difference that is obtained by the gyroscope. 30 | * Basically it contains a rotational difference encoded into a quaternion. 31 | * 32 | * To obtain the absolute orientation one must add this into an initial position by 33 | * multiplying it with another quaternion 34 | */ 35 | private final Quaternion deltaQuaternion = new Quaternion(); 36 | 37 | /** 38 | * The Quaternions that contain the current rotation (Angle and axis in Quaternion format) of the Gyroscope 39 | */ 40 | private Quaternion quaternionGyroscope = new Quaternion(); 41 | 42 | /** 43 | * The quaternion that contains the absolute orientation as obtained by the rotationVector sensor. 44 | */ 45 | private Quaternion quaternionRotationVector = new Quaternion(); 46 | 47 | /** 48 | * The time-stamp being used to record the time when the last gyroscope event occurred. 49 | */ 50 | private long timestamp; 51 | 52 | /** 53 | * This is a filter-threshold for discarding Gyroscope measurements that are below a certain level and 54 | * potentially are only noise and not real motion. Values from the gyroscope are usually between 0 (stop) and 55 | * 10 (rapid rotation), so 0.1 seems to be a reasonable threshold to filter noise (usually smaller than 0.1) and 56 | * real motion (usually > 0.1). Note that there is a chance of missing real motion, if the use is turning the 57 | * device really slowly, so this value has to find a balance between accepting noise (threshold = 0) and missing 58 | * slow user-action (threshold > 0.5). 0.1 seems to work fine for most applications. 59 | * 60 | */ 61 | private static final double EPSILON = 0.1f; 62 | 63 | /** 64 | * Value giving the total velocity of the gyroscope (will be high, when the device is moving fast and low when 65 | * the device is standing still). This is usually a value between 0 and 10 for normal motion. Heavy shaking can 66 | * increase it to about 25. Keep in mind, that these values are time-depended, so changing the sampling rate of 67 | * the sensor will affect this value! 68 | */ 69 | private double gyroscopeRotationVelocity = 0; 70 | 71 | /** 72 | * Flag indicating, whether the orientations were initialised from the rotation vector or not. If false, the 73 | * gyroscope can not be used (since it's only meaningful to calculate differences from an initial state). If 74 | * true, 75 | * the gyroscope can be used normally. 76 | */ 77 | private boolean positionInitialised = false; 78 | 79 | /** 80 | * Counter that sums the number of consecutive frames, where the rotationVector and the gyroscope were 81 | * significantly different (and the dot-product was smaller than 0.7). This event can either happen when the 82 | * angles of the rotation vector explode (e.g. during fast tilting) or when the device was shaken heavily and 83 | * the gyroscope is now completely off. 84 | */ 85 | private int panicCounter; 86 | 87 | /** 88 | * This weight determines indirectly how much the rotation sensor will be used to correct. This weight will be 89 | * multiplied by the velocity to obtain the actual weight. (in sensor-fusion-scenario 2 - 90 | * SensorSelection.GyroscopeAndRotationVector2). 91 | * Must be a value between 0 and approx. 0.04 (because, if multiplied with a velocity of up to 25, should be still 92 | * less than 1, otherwise the SLERP will not correctly interpolate). Should be close to zero. 93 | */ 94 | private static final float INDIRECT_INTERPOLATION_WEIGHT = 0.01f; 95 | 96 | /** 97 | * The threshold that indicates an outlier of the rotation vector. If the dot-product between the two vectors 98 | * (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1, 99 | * if they are exactly the same) the system falls back to the gyroscope values only and just ignores the 100 | * rotation vector. 101 | * 102 | * This value should be quite high (> 0.7) to filter even the slightest discrepancies that causes jumps when 103 | * tiling the device. Possible values are between 0 and 1, where a value close to 1 means that even a very small 104 | * difference between the two sensors will be treated as outlier, whereas a value close to zero means that the 105 | * almost any discrepancy between the two sensors is tolerated. 106 | */ 107 | private static final float OUTLIER_THRESHOLD = 0.85f; 108 | 109 | /** 110 | * The threshold that indicates a massive discrepancy between the rotation vector and the gyroscope orientation. 111 | * If the dot-product between the two vectors 112 | * (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1, if 113 | * they are exactly the same), the system will start increasing the panic counter (that probably indicates a 114 | * gyroscope failure). 115 | * 116 | * This value should be lower than OUTLIER_THRESHOLD (0.5 - 0.7) to only start increasing the panic counter, 117 | * when there is a huge discrepancy between the two fused sensors. 118 | */ 119 | private static final float OUTLIER_PANIC_THRESHOLD = 0.75f; 120 | 121 | /** 122 | * The threshold that indicates that a chaos state has been established rather than just a temporary peak in the 123 | * rotation vector (caused by exploding angled during fast tilting). 124 | * 125 | * If the chaosCounter is bigger than this threshold, the current position will be reset to whatever the 126 | * rotation vector indicates. 127 | */ 128 | private static final int PANIC_THRESHOLD = 60; 129 | 130 | /** 131 | * Some temporary variable to save allocations. 132 | */ 133 | final private float[] temporaryQuaternion = new float[4]; 134 | final private Quaternion correctedQuaternion = new Quaternion(); 135 | final private Quaternion interpolatedQuaternion = new Quaternion(); 136 | 137 | /** 138 | * Initialises a new ImprovedOrientationSensor2Provider 139 | * 140 | * @param sensorManager The android sensor manager 141 | */ 142 | public ImprovedOrientationSensor2Provider(SensorManager sensorManager) { 143 | super(sensorManager); 144 | 145 | //Add the gyroscope and rotation Vector 146 | sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)); 147 | sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)); 148 | } 149 | 150 | @Override 151 | public void onSensorChanged(SensorEvent event) { 152 | 153 | if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { 154 | // Process rotation vector (just safe it) 155 | // Calculate angle. Starting with API_18, Android will provide this value as event.values[3], but if not, we have to calculate it manually. 156 | SensorManager.getQuaternionFromVector(temporaryQuaternion, event.values); 157 | 158 | // Store in quaternion 159 | quaternionRotationVector.setXYZW(temporaryQuaternion[1], temporaryQuaternion[2], temporaryQuaternion[3], -temporaryQuaternion[0]); 160 | if (!positionInitialised) { 161 | // Override 162 | quaternionGyroscope.set(quaternionRotationVector); 163 | positionInitialised = true; 164 | } 165 | 166 | } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { 167 | // Process Gyroscope and perform fusion 168 | 169 | // This timestep's delta rotation to be multiplied by the current rotation 170 | // after computing it from the gyro sample data. 171 | if (timestamp != 0) { 172 | final float dT = (event.timestamp - timestamp) * NS2S; 173 | // Axis of the rotation sample, not normalized yet. 174 | float axisX = event.values[0]; 175 | float axisY = event.values[1]; 176 | float axisZ = event.values[2]; 177 | 178 | // Calculate the angular speed of the sample 179 | gyroscopeRotationVelocity = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); 180 | 181 | // Normalize the rotation vector if it's big enough to get the axis 182 | if (gyroscopeRotationVelocity > EPSILON) { 183 | axisX /= gyroscopeRotationVelocity; 184 | axisY /= gyroscopeRotationVelocity; 185 | axisZ /= gyroscopeRotationVelocity; 186 | } 187 | 188 | // Integrate around this axis with the angular speed by the timestep 189 | // in order to get a delta rotation from this sample over the timestep 190 | // We will convert this axis-angle representation of the delta rotation 191 | // into a quaternion before turning it into the rotation matrix. 192 | double thetaOverTwo = gyroscopeRotationVelocity * dT / 2.0f; 193 | double sinThetaOverTwo = Math.sin(thetaOverTwo); 194 | double cosThetaOverTwo = Math.cos(thetaOverTwo); 195 | deltaQuaternion.setX((float) (sinThetaOverTwo * axisX)); 196 | deltaQuaternion.setY((float) (sinThetaOverTwo * axisY)); 197 | deltaQuaternion.setZ((float) (sinThetaOverTwo * axisZ)); 198 | deltaQuaternion.setW(-(float) cosThetaOverTwo); 199 | 200 | // Move current gyro orientation 201 | deltaQuaternion.multiplyByQuat(quaternionGyroscope, quaternionGyroscope); 202 | 203 | // Calculate dot-product to calculate whether the two orientation sensors have diverged 204 | // (if the dot-product is closer to 0 than to 1), because it should be close to 1 if both are the same. 205 | float dotProd = quaternionGyroscope.dotProduct(quaternionRotationVector); 206 | 207 | // If they have diverged, rely on gyroscope only (this happens on some devices when the rotation vector "jumps"). 208 | if (Math.abs(dotProd) < OUTLIER_THRESHOLD) { 209 | // Increase panic counter 210 | if (Math.abs(dotProd) < OUTLIER_PANIC_THRESHOLD) { 211 | panicCounter++; 212 | } 213 | 214 | // Directly use Gyro 215 | setOrientationQuaternionAndMatrix(quaternionGyroscope); 216 | 217 | } else { 218 | // Both are nearly saying the same. Perform normal fusion. 219 | 220 | // Interpolate with a fixed weight between the two absolute quaternions obtained from gyro and rotation vector sensors 221 | // The weight should be quite low, so the rotation vector corrects the gyro only slowly, and the output keeps responsive. 222 | quaternionGyroscope.slerp(quaternionRotationVector, interpolatedQuaternion, 223 | (float) (INDIRECT_INTERPOLATION_WEIGHT * gyroscopeRotationVelocity)); 224 | 225 | // Use the interpolated value between gyro and rotationVector 226 | setOrientationQuaternionAndMatrix(interpolatedQuaternion); 227 | // Override current gyroscope-orientation 228 | quaternionGyroscope.copyVec4(interpolatedQuaternion); 229 | 230 | // Reset the panic counter because both sensors are saying the same again 231 | panicCounter = 0; 232 | } 233 | 234 | if (panicCounter > PANIC_THRESHOLD) { 235 | Log.d("Rotation Vector", 236 | "Panic counter is bigger than threshold; this indicates a Gyroscope failure. Panic reset is imminent."); 237 | 238 | if (gyroscopeRotationVelocity < 3) { 239 | Log.d("Rotation Vector", 240 | "Performing Panic-reset. Resetting orientation to rotation-vector value."); 241 | 242 | // Manually set position to whatever rotation vector says. 243 | setOrientationQuaternionAndMatrix(quaternionRotationVector); 244 | // Override current gyroscope-orientation with corrected value 245 | quaternionGyroscope.copyVec4(quaternionRotationVector); 246 | 247 | panicCounter = 0; 248 | } else { 249 | Log.d("Rotation Vector", 250 | String.format( 251 | "Panic reset delayed due to ongoing motion (user is still shaking the device). Gyroscope Velocity: %.2f > 3", 252 | gyroscopeRotationVelocity)); 253 | } 254 | } 255 | } 256 | timestamp = event.timestamp; 257 | } 258 | } 259 | 260 | /** 261 | * Sets the output quaternion and matrix with the provided quaternion and synchronises the setting 262 | * 263 | * @param quaternion The Quaternion to set (the result of the sensor fusion) 264 | */ 265 | private void setOrientationQuaternionAndMatrix(Quaternion quaternion) { 266 | correctedQuaternion.set(quaternion); 267 | // We inverted w in the deltaQuaternion, because currentOrientationQuaternion required it. 268 | // Before converting it back to matrix representation, we need to revert this process 269 | correctedQuaternion.w(-correctedQuaternion.w()); 270 | 271 | synchronized (synchronizationToken) { 272 | // Use gyro only 273 | currentOrientationQuaternion.copyVec4(quaternion); 274 | 275 | // Set the rotation matrix as well to have both representations 276 | SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix, correctedQuaternion.array()); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/provider/OrientationProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package dac.zjms.com.compass.provider; 5 | 6 | import android.hardware.Sensor; 7 | import android.hardware.SensorEventListener; 8 | import android.hardware.SensorManager; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import dac.zjms.com.compass.representation.MatrixF4x4; 14 | import dac.zjms.com.compass.representation.Quaternion; 15 | 16 | /** 17 | * Classes implementing this interface provide an orientation of the device 18 | * either by directly accessing hardware, using Android sensor fusion or fusing 19 | * sensors itself. 20 | * 21 | * The orientation can be provided as rotation matrix or quaternion. 22 | * 23 | * @author Alexander Pacha 24 | * 25 | */ 26 | public abstract class OrientationProvider implements SensorEventListener { 27 | /** 28 | * Sync-token for syncing read/write to sensor-data from sensor manager and 29 | * fusion algorithm 30 | */ 31 | protected final Object synchronizationToken = new Object(); 32 | 33 | /** 34 | * The list of sensors used by this provider 35 | */ 36 | protected List sensorList = new ArrayList(); 37 | 38 | /** 39 | * The matrix that holds the current rotation 40 | */ 41 | protected final MatrixF4x4 currentOrientationRotationMatrix; 42 | 43 | /** 44 | * The quaternion that holds the current rotation 45 | */ 46 | protected final Quaternion currentOrientationQuaternion; 47 | 48 | /** 49 | * The sensor manager for accessing android sensors 50 | */ 51 | protected SensorManager sensorManager; 52 | 53 | /** 54 | * Initialises a new OrientationProvider 55 | * 56 | * @param sensorManager 57 | * The android sensor manager 58 | */ 59 | public OrientationProvider(SensorManager sensorManager) { 60 | this.sensorManager = sensorManager; 61 | 62 | // Initialise with identity 63 | currentOrientationRotationMatrix = new MatrixF4x4(); 64 | 65 | // Initialise with identity 66 | currentOrientationQuaternion = new Quaternion(); 67 | } 68 | 69 | /** 70 | * Starts the sensor fusion (e.g. when resuming the activity) 71 | */ 72 | public void start() { 73 | // enable our sensor when the activity is resumed, ask for 74 | // 10 ms updates. 75 | for (Sensor sensor : sensorList) { 76 | // enable our sensors when the activity is resumed, ask for 77 | // 20 ms updates (Sensor_delay_game) 78 | sensorManager.registerListener(this, sensor, 79 | SensorManager.SENSOR_DELAY_GAME); 80 | } 81 | } 82 | 83 | /** 84 | * Stops the sensor fusion (e.g. when pausing/suspending the activity) 85 | */ 86 | public void stop() { 87 | // make sure to turn our sensors off when the activity is paused 88 | for (Sensor sensor : sensorList) { 89 | sensorManager.unregisterListener(this, sensor); 90 | } 91 | } 92 | 93 | @Override 94 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 95 | // Not doing anything 96 | } 97 | 98 | /** 99 | * Get the current rotation of the device in the rotation matrix format (4x4 matrix) 100 | */ 101 | public void getRotationMatrix(MatrixF4x4 matrix) { 102 | synchronized (synchronizationToken) { 103 | matrix.set(currentOrientationRotationMatrix); 104 | } 105 | } 106 | 107 | /** 108 | * Get the current rotation of the device in the quaternion format (vector4f) 109 | */ 110 | public void getQuaternion(Quaternion quaternion) { 111 | synchronized (synchronizationToken) { 112 | quaternion.set(currentOrientationQuaternion); 113 | } 114 | } 115 | 116 | /** 117 | * Get the current rotation of the device in the Euler angles 118 | */ 119 | public void getEulerAngles(float angles[]) { 120 | synchronized (synchronizationToken) { 121 | SensorManager.getOrientation(currentOrientationRotationMatrix.matrix, angles); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/representation/Matrix.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.representation; 2 | 3 | /** 4 | * Matrix math utilities. These methods operate on OpenGL ES format 5 | * matrices and vectors stored in float arrays. 6 | * 7 | * Matrices are 4 x 4 column-vector matrices stored in column-major 8 | * order: 9 | * 10 | *
 11 |  *  m[offset +  0] m[offset +  4] m[offset +  8] m[offset + 12]
 12 |  *  m[offset +  1] m[offset +  5] m[offset +  9] m[offset + 13]
 13 |  *  m[offset +  2] m[offset +  6] m[offset + 10] m[offset + 14]
 14 |  *  m[offset +  3] m[offset +  7] m[offset + 11] m[offset + 15]
 15 |  * 
16 | * 17 | * Vectors are 4 row x 1 column column-vectors stored in order: 18 | * 19 | *
 20 |  * v[offset + 0]
 21 |  * v[offset + 1]
 22 |  * v[offset + 2]
 23 |  * v[offset + 3]
 24 |  * 
25 | * 26 | */ 27 | public class Matrix { 28 | 29 | /** 30 | * Temporary memory for operations that need temporary matrix data. 31 | */ 32 | private static final float[] TEMP_MATRIX_ARRAY = new float[32]; 33 | 34 | /** 35 | * Multiply two 4x4 matrices together and store the result in a third 4x4 36 | * matrix. In matrix notation: result = lhs x rhs. Due to the way 37 | * matrix multiplication works, the result matrix will have the same 38 | * effect as first multiplying by the rhs matrix, then multiplying by 39 | * the lhs matrix. This is the opposite of what you might expect. 40 | * 41 | * The same float array may be passed for result, lhs, and/or rhs. However, 42 | * the result element values are undefined if the result elements overlap 43 | * either the lhs or rhs elements. 44 | * 45 | * @param result The float array that holds the result. 46 | * @param resultOffset The offset into the result array where the result is 47 | * stored. 48 | * @param lhs The float array that holds the left-hand-side matrix. 49 | * @param lhsOffset The offset into the lhs array where the lhs is stored 50 | * @param rhs The float array that holds the right-hand-side matrix. 51 | * @param rhsOffset The offset into the rhs array where the rhs is stored. 52 | * 53 | * @throws IllegalArgumentException if result, lhs, or rhs are null, or if 54 | * resultOffset + 16 > result.length or lhsOffset + 16 > lhs.length or 55 | * rhsOffset + 16 > rhs.length. 56 | */ 57 | /** 58 | * public static void multiplyMM(float[] result, int resultOffset, 59 | * float[] lhs, int lhsOffset, float[] rhs, int rhsOffset){ 60 | * android.opengl.Matrix.multiplyMM(result, resultOffset, lhs, lhsOffset, rhs, rhsOffset); 61 | * } 62 | */ 63 | 64 | public static void multiplyMM(float[] output, int outputOffset, float[] lhs, int lhsOffset, float[] rhs, 65 | int rhsOffset) { 66 | //for(int i = 0; i < 4; i++){ 67 | // for(int j = 0; j < 4; j++){ 68 | 69 | // int k = i * 4; 70 | // output[outputOffset + 0 + j] += lhs[lhsOffset + k + j] * rhs[rhsOffset + 0 * 4 + i]; 71 | // output[outputOffset + 1 * 4 + j] += lhs[lhsOffset +k + j] * rhs[rhsOffset + 1 * 4 + i]; 72 | // output[outputOffset + 2 * 4 + j] += lhs[lhsOffset +k + j] * rhs[rhsOffset + 2 * 4 + i]; 73 | // output[outputOffset + 3 * 4 + j] += lhs[lhsOffset +k + j] * rhs[rhsOffset + 3 * 4 + i]; 74 | // } 75 | //} 76 | output[outputOffset + 0] = lhs[lhsOffset + 0] * rhs[rhsOffset + 0] + lhs[lhsOffset + 4] * rhs[rhsOffset + 1] 77 | + lhs[lhsOffset + 8] * rhs[rhsOffset + 2] + lhs[lhsOffset + 12] * rhs[rhsOffset + 3]; 78 | output[outputOffset + 1] = lhs[lhsOffset + 1] * rhs[rhsOffset + 0] + lhs[lhsOffset + 5] * rhs[rhsOffset + 1] 79 | + lhs[lhsOffset + 9] * rhs[rhsOffset + 2] + lhs[lhsOffset + 13] * rhs[rhsOffset + 3]; 80 | output[outputOffset + 2] = lhs[lhsOffset + 2] * rhs[rhsOffset + 0] + lhs[lhsOffset + 6] * rhs[rhsOffset + 1] 81 | + lhs[lhsOffset + 10] * rhs[rhsOffset + 2] + lhs[lhsOffset + 14] * rhs[rhsOffset + 3]; 82 | output[outputOffset + 3] = lhs[lhsOffset + 3] * rhs[rhsOffset + 0] + lhs[lhsOffset + 7] * rhs[rhsOffset + 1] 83 | + lhs[lhsOffset + 11] * rhs[rhsOffset + 2] + lhs[lhsOffset + 15] * rhs[rhsOffset + 3]; 84 | 85 | output[outputOffset + 4] = lhs[lhsOffset + 0] * rhs[rhsOffset + 4] + lhs[lhsOffset + 4] * rhs[rhsOffset + 5] 86 | + lhs[lhsOffset + 8] * rhs[rhsOffset + 6] + lhs[lhsOffset + 12] * rhs[rhsOffset + 7]; 87 | output[outputOffset + 5] = lhs[lhsOffset + 1] * rhs[rhsOffset + 4] + lhs[lhsOffset + 5] * rhs[rhsOffset + 5] 88 | + lhs[lhsOffset + 9] * rhs[rhsOffset + 6] + lhs[lhsOffset + 13] * rhs[rhsOffset + 7]; 89 | output[outputOffset + 6] = lhs[lhsOffset + 2] * rhs[rhsOffset + 4] + lhs[lhsOffset + 6] * rhs[rhsOffset + 5] 90 | + lhs[lhsOffset + 10] * rhs[rhsOffset + 6] + lhs[lhsOffset + 14] * rhs[rhsOffset + 7]; 91 | output[outputOffset + 7] = lhs[lhsOffset + 3] * rhs[rhsOffset + 4] + lhs[lhsOffset + 7] * rhs[rhsOffset + 5] 92 | + lhs[lhsOffset + 11] * rhs[rhsOffset + 6] + lhs[lhsOffset + 15] * rhs[rhsOffset + 7]; 93 | 94 | output[outputOffset + 8] = lhs[lhsOffset + 0] * rhs[rhsOffset + 8] + lhs[lhsOffset + 4] * rhs[rhsOffset + 9] 95 | + lhs[lhsOffset + 8] * rhs[rhsOffset + 10] + lhs[lhsOffset + 12] * rhs[rhsOffset + 11]; 96 | output[outputOffset + 9] = lhs[lhsOffset + 1] * rhs[rhsOffset + 8] + lhs[lhsOffset + 5] * rhs[rhsOffset + 9] 97 | + lhs[lhsOffset + 9] * rhs[rhsOffset + 10] + lhs[lhsOffset + 13] * rhs[rhsOffset + 11]; 98 | output[outputOffset + 10] = lhs[lhsOffset + 2] * rhs[rhsOffset + 8] + lhs[lhsOffset + 6] * rhs[rhsOffset + 9] 99 | + lhs[lhsOffset + 10] * rhs[rhsOffset + 10] + lhs[lhsOffset + 14] * rhs[rhsOffset + 11]; 100 | output[outputOffset + 11] = lhs[lhsOffset + 3] * rhs[rhsOffset + 8] + lhs[lhsOffset + 7] * rhs[rhsOffset + 9] 101 | + lhs[lhsOffset + 11] * rhs[rhsOffset + 10] + lhs[lhsOffset + 15] * rhs[rhsOffset + 11]; 102 | 103 | output[outputOffset + 12] = lhs[lhsOffset + 0] * rhs[rhsOffset + 12] + lhs[lhsOffset + 4] * rhs[rhsOffset + 13] 104 | + lhs[lhsOffset + 8] * rhs[rhsOffset + 14] + lhs[lhsOffset + 12] * rhs[rhsOffset + 15]; 105 | output[outputOffset + 13] = lhs[lhsOffset + 1] * rhs[rhsOffset + 12] + lhs[lhsOffset + 5] * rhs[rhsOffset + 13] 106 | + lhs[lhsOffset + 9] * rhs[rhsOffset + 14] + lhs[lhsOffset + 13] * rhs[rhsOffset + 15]; 107 | output[outputOffset + 14] = lhs[lhsOffset + 2] * rhs[rhsOffset + 12] + lhs[lhsOffset + 6] * rhs[rhsOffset + 13] 108 | + lhs[lhsOffset + 10] * rhs[rhsOffset + 14] + lhs[lhsOffset + 14] * rhs[rhsOffset + 15]; 109 | output[outputOffset + 15] = lhs[lhsOffset + 3] * rhs[rhsOffset + 12] + lhs[lhsOffset + 7] * rhs[rhsOffset + 13] 110 | + lhs[lhsOffset + 11] * rhs[rhsOffset + 14] + lhs[lhsOffset + 15] * rhs[rhsOffset + 15]; 111 | } 112 | 113 | public static void multiplyMM(float[] output, float[] lhs, float[] rhs) { 114 | output[0] = lhs[0] * rhs[0] + lhs[4] * rhs[1] + lhs[8] * rhs[2] + lhs[12] * rhs[3]; 115 | output[1] = lhs[1] * rhs[0] + lhs[5] * rhs[1] + lhs[9] * rhs[2] + lhs[13] * rhs[3]; 116 | output[2] = lhs[2] * rhs[0] + lhs[6] * rhs[1] + lhs[10] * rhs[2] + lhs[14] * rhs[3]; 117 | output[3] = lhs[3] * rhs[0] + lhs[7] * rhs[1] + lhs[11] * rhs[2] + lhs[15] * rhs[3]; 118 | 119 | output[4] = lhs[0] * rhs[4] + lhs[4] * rhs[5] + lhs[8] * rhs[6] + lhs[12] * rhs[7]; 120 | output[5] = lhs[1] * rhs[4] + lhs[5] * rhs[5] + lhs[9] * rhs[6] + lhs[13] * rhs[7]; 121 | output[6] = lhs[2] * rhs[4] + lhs[6] * rhs[5] + lhs[10] * rhs[6] + lhs[14] * rhs[7]; 122 | output[7] = lhs[3] * rhs[4] + lhs[7] * rhs[5] + lhs[11] * rhs[6] + lhs[15] * rhs[7]; 123 | 124 | output[8] = lhs[0] * rhs[8] + lhs[4] * rhs[9] + lhs[8] * rhs[10] + lhs[12] * rhs[11]; 125 | output[9] = lhs[1] * rhs[8] + lhs[5] * rhs[9] + lhs[9] * rhs[10] + lhs[13] * rhs[11]; 126 | output[10] = lhs[2] * rhs[8] + lhs[6] * rhs[9] + lhs[10] * rhs[10] + lhs[14] * rhs[11]; 127 | output[11] = lhs[3] * rhs[8] + lhs[7] * rhs[9] + lhs[11] * rhs[10] + lhs[15] * rhs[11]; 128 | 129 | output[12] = lhs[0] * rhs[12] + lhs[4] * rhs[13] + lhs[8] * rhs[14] + lhs[12] * rhs[15]; 130 | output[13] = lhs[1] * rhs[12] + lhs[5] * rhs[13] + lhs[9] * rhs[14] + lhs[13] * rhs[15]; 131 | output[14] = lhs[2] * rhs[12] + lhs[6] * rhs[13] + lhs[10] * rhs[14] + lhs[14] * rhs[15]; 132 | output[15] = lhs[3] * rhs[12] + lhs[7] * rhs[13] + lhs[11] * rhs[14] + lhs[15] * rhs[15]; 133 | } 134 | 135 | /** 136 | * Multiply a 4 element vector by a 4x4 matrix and store the result in a 4 137 | * element column vector. In matrix notation: result = lhs x rhs 138 | * 139 | * The same float array may be passed for resultVec, lhsMat, and/or rhsVec. 140 | * However, the resultVec element values are undefined if the resultVec 141 | * elements overlap either the lhsMat or rhsVec elements. 142 | * 143 | * @param resultVec The float array that holds the result vector. 144 | * @param resultVecOffset The offset into the result array where the result 145 | * vector is stored. 146 | * @param lhsMat The float array that holds the left-hand-side matrix. 147 | * @param lhsMatOffset The offset into the lhs array where the lhs is stored 148 | * @param rhsVec The float array that holds the right-hand-side vector. 149 | * @param rhsVecOffset The offset into the rhs vector where the rhs vector 150 | * is stored. 151 | * 152 | * @throws IllegalArgumentException if resultVec, lhsMat, 153 | * or rhsVec are null, or if resultVecOffset + 4 > resultVec.length 154 | * or lhsMatOffset + 16 > lhsMat.length or 155 | * rhsVecOffset + 4 > rhsVec.length. 156 | */ 157 | /* public static void multiplyMV(float[] resultVec, 158 | * int resultVecOffset, float[] lhsMat, int lhsMatOffset, 159 | * float[] rhsVec, int rhsVecOffset){ 160 | * android.opengl.Matrix.multiplyMV(resultVec, resultVecOffset, lhsMat, lhsMatOffset, rhsVec, rhsVecOffset); 161 | * } */ 162 | public static void multiplyMV(float[] output, int outputOffset, float[] lhs, int lhsOffset, float[] rhs, 163 | int rhsOffset) { 164 | /* wrong implementation (this is for row major matrices) 165 | * output[outputOffset +0] = lhs[lhsOffset + 0] * rhs[rhsOffset + 0] + lhs[lhsOffset + 1] * rhs[rhsOffset + 1] 166 | * + lhs[lhsOffset + 2] * rhs[rhsOffset + 2] + lhs[lhsOffset + 3] * rhs[rhsOffset + 3]; 167 | * output[outputOffset +1] = lhs[lhsOffset + 4] * rhs[rhsOffset + 0] + lhs[lhsOffset + 5] * rhs[rhsOffset + 1] + 168 | * lhs[lhsOffset + 6] * rhs[rhsOffset + 2] + lhs[lhsOffset + 7] * rhs[rhsOffset + 3]; 169 | * output[outputOffset +2] = lhs[lhsOffset + 8] * rhs[rhsOffset + 0] + lhs[lhsOffset + 9] * rhs[rhsOffset + 1] + 170 | * lhs[lhsOffset + 10] * rhs[rhsOffset + 2] + lhs[lhsOffset + 11] * rhs[rhsOffset + 3]; 171 | * output[outputOffset +3] = lhs[lhsOffset + 12] * rhs[rhsOffset + 0] + lhs[lhsOffset + 13] * rhs[rhsOffset + 1] 172 | * + lhs[lhsOffset + 14] * rhs[rhsOffset + 2] + lhs[lhsOffset + 15] * rhs[rhsOffset + 3]; */ 173 | // correct implementation for column major matrices (which is for OpenGL) 174 | output[outputOffset + 0] = lhs[lhsOffset + 0] * rhs[rhsOffset + 0] + lhs[lhsOffset + 4] * rhs[rhsOffset + 1] 175 | + lhs[lhsOffset + 8] * rhs[rhsOffset + 2] + lhs[lhsOffset + 12] * rhs[rhsOffset + 3]; 176 | output[outputOffset + 1] = lhs[lhsOffset + 1] * rhs[rhsOffset + 0] + lhs[lhsOffset + 5] * rhs[rhsOffset + 1] 177 | + lhs[lhsOffset + 9] * rhs[rhsOffset + 2] + lhs[lhsOffset + 13] * rhs[rhsOffset + 3]; 178 | output[outputOffset + 2] = lhs[lhsOffset + 2] * rhs[rhsOffset + 0] + lhs[lhsOffset + 6] * rhs[rhsOffset + 1] 179 | + lhs[lhsOffset + 10] * rhs[rhsOffset + 2] + lhs[lhsOffset + 14] * rhs[rhsOffset + 3]; 180 | output[outputOffset + 3] = lhs[lhsOffset + 3] * rhs[rhsOffset + 0] + lhs[lhsOffset + 7] * rhs[rhsOffset + 1] 181 | + lhs[lhsOffset + 11] * rhs[rhsOffset + 2] + lhs[lhsOffset + 15] * rhs[rhsOffset + 3]; 182 | 183 | } 184 | 185 | public static void multiplyMV(float[] outputV, float[] inputM, float[] inputV) { 186 | outputV[0] = inputM[0] * inputV[0] + inputM[4] * inputV[1] + inputM[8] * inputV[2] + inputM[12] * inputV[3]; 187 | outputV[1] = inputM[1] * inputV[0] + inputM[5] * inputV[1] + inputM[9] * inputV[2] + inputM[13] * inputV[3]; 188 | outputV[2] = inputM[2] * inputV[0] + inputM[6] * inputV[1] + inputM[10] * inputV[2] + inputM[14] * inputV[3]; 189 | outputV[3] = inputM[3] * inputV[0] + inputM[7] * inputV[1] + inputM[11] * inputV[2] + inputM[15] * inputV[3]; 190 | } 191 | 192 | public static void multiplyMV3(float[] outputV, float[] inputM, float[] inputV, float w) { 193 | outputV[0] = inputM[0] * inputV[0] + inputM[4] * inputV[1] + inputM[8] * inputV[2] + inputM[12] * w; 194 | outputV[1] = inputM[1] * inputV[0] + inputM[5] * inputV[1] + inputM[9] * inputV[2] + inputM[13] * w; 195 | outputV[2] = inputM[2] * inputV[0] + inputM[6] * inputV[1] + inputM[10] * inputV[2] + inputM[14] * w; 196 | } 197 | 198 | /** 199 | * Transposes a 4 x 4 matrix. 200 | * 201 | * @param mTrans the array that holds the output inverted matrix 202 | * @param mTransOffset an offset into mInv where the inverted matrix is 203 | * stored. 204 | * @param m the input array 205 | * @param mOffset an offset into m where the matrix is stored. 206 | */ 207 | public static void transposeM(float[] mTrans, int mTransOffset, float[] m, int mOffset) { 208 | for (int i = 0; i < 4; i++) { 209 | int mBase = i * 4 + mOffset; 210 | mTrans[i + mTransOffset] = m[mBase]; 211 | mTrans[i + 4 + mTransOffset] = m[mBase + 1]; 212 | mTrans[i + 8 + mTransOffset] = m[mBase + 2]; 213 | mTrans[i + 12 + mTransOffset] = m[mBase + 3]; 214 | } 215 | } 216 | 217 | /** 218 | * Inverts a 4 x 4 matrix. 219 | * 220 | * @param mInv the array that holds the output inverted matrix 221 | * @param mInvOffset an offset into mInv where the inverted matrix is 222 | * stored. 223 | * @param m the input array 224 | * @param mOffset an offset into m where the matrix is stored. 225 | * @return true if the matrix could be inverted, false if it could not. 226 | */ 227 | public static boolean invertM(float[] mInv, int mInvOffset, float[] m, int mOffset) { 228 | // Invert a 4 x 4 matrix using Cramer's Rule 229 | 230 | // transpose matrix 231 | final float src0 = m[mOffset + 0]; 232 | final float src4 = m[mOffset + 1]; 233 | final float src8 = m[mOffset + 2]; 234 | final float src12 = m[mOffset + 3]; 235 | 236 | final float src1 = m[mOffset + 4]; 237 | final float src5 = m[mOffset + 5]; 238 | final float src9 = m[mOffset + 6]; 239 | final float src13 = m[mOffset + 7]; 240 | 241 | final float src2 = m[mOffset + 8]; 242 | final float src6 = m[mOffset + 9]; 243 | final float src10 = m[mOffset + 10]; 244 | final float src14 = m[mOffset + 11]; 245 | 246 | final float src3 = m[mOffset + 12]; 247 | final float src7 = m[mOffset + 13]; 248 | final float src11 = m[mOffset + 14]; 249 | final float src15 = m[mOffset + 15]; 250 | 251 | // calculate pairs for first 8 elements (cofactors) 252 | final float atmp0 = src10 * src15; 253 | final float atmp1 = src11 * src14; 254 | final float atmp2 = src9 * src15; 255 | final float atmp3 = src11 * src13; 256 | final float atmp4 = src9 * src14; 257 | final float atmp5 = src10 * src13; 258 | final float atmp6 = src8 * src15; 259 | final float atmp7 = src11 * src12; 260 | final float atmp8 = src8 * src14; 261 | final float atmp9 = src10 * src12; 262 | final float atmp10 = src8 * src13; 263 | final float atmp11 = src9 * src12; 264 | 265 | // calculate first 8 elements (cofactors) 266 | final float dst0 = (atmp0 * src5 + atmp3 * src6 + atmp4 * src7) - (atmp1 * src5 + atmp2 * src6 + atmp5 * src7); 267 | final float dst1 = (atmp1 * src4 + atmp6 * src6 + atmp9 * src7) - (atmp0 * src4 + atmp7 * src6 + atmp8 * src7); 268 | final float dst2 = (atmp2 * src4 + atmp7 * src5 + atmp10 * src7) 269 | - (atmp3 * src4 + atmp6 * src5 + atmp11 * src7); 270 | final float dst3 = (atmp5 * src4 + atmp8 * src5 + atmp11 * src6) 271 | - (atmp4 * src4 + atmp9 * src5 + atmp10 * src6); 272 | final float dst4 = (atmp1 * src1 + atmp2 * src2 + atmp5 * src3) - (atmp0 * src1 + atmp3 * src2 + atmp4 * src3); 273 | final float dst5 = (atmp0 * src0 + atmp7 * src2 + atmp8 * src3) - (atmp1 * src0 + atmp6 * src2 + atmp9 * src3); 274 | final float dst6 = (atmp3 * src0 + atmp6 * src1 + atmp11 * src3) 275 | - (atmp2 * src0 + atmp7 * src1 + atmp10 * src3); 276 | final float dst7 = (atmp4 * src0 + atmp9 * src1 + atmp10 * src2) 277 | - (atmp5 * src0 + atmp8 * src1 + atmp11 * src2); 278 | 279 | // calculate pairs for second 8 elements (cofactors) 280 | final float btmp0 = src2 * src7; 281 | final float btmp1 = src3 * src6; 282 | final float btmp2 = src1 * src7; 283 | final float btmp3 = src3 * src5; 284 | final float btmp4 = src1 * src6; 285 | final float btmp5 = src2 * src5; 286 | final float btmp6 = src0 * src7; 287 | final float btmp7 = src3 * src4; 288 | final float btmp8 = src0 * src6; 289 | final float btmp9 = src2 * src4; 290 | final float btmp10 = src0 * src5; 291 | final float btmp11 = src1 * src4; 292 | 293 | // calculate second 8 elements (cofactors) 294 | final float dst8 = (btmp0 * src13 + btmp3 * src14 + btmp4 * src15) 295 | - (btmp1 * src13 + btmp2 * src14 + btmp5 * src15); 296 | final float dst9 = (btmp1 * src12 + btmp6 * src14 + btmp9 * src15) 297 | - (btmp0 * src12 + btmp7 * src14 + btmp8 * src15); 298 | final float dst10 = (btmp2 * src12 + btmp7 * src13 + btmp10 * src15) 299 | - (btmp3 * src12 + btmp6 * src13 + btmp11 * src15); 300 | final float dst11 = (btmp5 * src12 + btmp8 * src13 + btmp11 * src14) 301 | - (btmp4 * src12 + btmp9 * src13 + btmp10 * src14); 302 | final float dst12 = (btmp2 * src10 + btmp5 * src11 + btmp1 * src9) 303 | - (btmp4 * src11 + btmp0 * src9 + btmp3 * src10); 304 | final float dst13 = (btmp8 * src11 + btmp0 * src8 + btmp7 * src10) 305 | - (btmp6 * src10 + btmp9 * src11 + btmp1 * src8); 306 | final float dst14 = (btmp6 * src9 + btmp11 * src11 + btmp3 * src8) 307 | - (btmp10 * src11 + btmp2 * src8 + btmp7 * src9); 308 | final float dst15 = (btmp10 * src10 + btmp4 * src8 + btmp9 * src9) 309 | - (btmp8 * src9 + btmp11 * src10 + btmp5 * src8); 310 | 311 | // calculate determinant 312 | final float det = src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3; 313 | 314 | if (det == 0.0f) { 315 | return false; 316 | } 317 | 318 | // calculate matrix inverse 319 | final float invdet = 1.0f / det; 320 | mInv[mInvOffset] = dst0 * invdet; 321 | mInv[1 + mInvOffset] = dst1 * invdet; 322 | mInv[2 + mInvOffset] = dst2 * invdet; 323 | mInv[3 + mInvOffset] = dst3 * invdet; 324 | 325 | mInv[4 + mInvOffset] = dst4 * invdet; 326 | mInv[5 + mInvOffset] = dst5 * invdet; 327 | mInv[6 + mInvOffset] = dst6 * invdet; 328 | mInv[7 + mInvOffset] = dst7 * invdet; 329 | 330 | mInv[8 + mInvOffset] = dst8 * invdet; 331 | mInv[9 + mInvOffset] = dst9 * invdet; 332 | mInv[10 + mInvOffset] = dst10 * invdet; 333 | mInv[11 + mInvOffset] = dst11 * invdet; 334 | 335 | mInv[12 + mInvOffset] = dst12 * invdet; 336 | mInv[13 + mInvOffset] = dst13 * invdet; 337 | mInv[14 + mInvOffset] = dst14 * invdet; 338 | mInv[15 + mInvOffset] = dst15 * invdet; 339 | 340 | return true; 341 | } 342 | 343 | /** 344 | * Computes an orthographic projection matrix. 345 | * 346 | * @param m returns the result 347 | * @param mOffset 348 | * @param left 349 | * @param right 350 | * @param bottom 351 | * @param top 352 | * @param near 353 | * @param far 354 | */ 355 | 356 | public static void orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, 357 | float far) { 358 | if (left == right) { 359 | throw new IllegalArgumentException("left == right"); 360 | } 361 | if (bottom == top) { 362 | throw new IllegalArgumentException("bottom == top"); 363 | } 364 | if (near == far) { 365 | throw new IllegalArgumentException("near == far"); 366 | } 367 | 368 | final float r_width = 1.0f / (right - left); 369 | final float r_height = 1.0f / (top - bottom); 370 | final float r_depth = 1.0f / (far - near); 371 | final float x = 2.0f * (r_width); 372 | final float y = 2.0f * (r_height); 373 | final float z = -2.0f * (r_depth); 374 | final float tx = -(right + left) * r_width; 375 | final float ty = -(top + bottom) * r_height; 376 | final float tz = -(far + near) * r_depth; 377 | m[mOffset + 0] = x; 378 | m[mOffset + 5] = y; 379 | m[mOffset + 10] = z; 380 | m[mOffset + 12] = tx; 381 | m[mOffset + 13] = ty; 382 | m[mOffset + 14] = tz; 383 | m[mOffset + 15] = 1.0f; 384 | m[mOffset + 1] = 0.0f; 385 | m[mOffset + 2] = 0.0f; 386 | m[mOffset + 3] = 0.0f; 387 | m[mOffset + 4] = 0.0f; 388 | m[mOffset + 6] = 0.0f; 389 | m[mOffset + 7] = 0.0f; 390 | m[mOffset + 8] = 0.0f; 391 | m[mOffset + 9] = 0.0f; 392 | m[mOffset + 11] = 0.0f; 393 | } 394 | 395 | /** 396 | * Define a projection matrix in terms of six clip planes 397 | * 398 | * @param m the float array that holds the perspective matrix 399 | * @param offset the offset into float array m where the perspective 400 | * matrix data is written 401 | * @param left 402 | * @param right 403 | * @param bottom 404 | * @param top 405 | * @param near 406 | * @param far 407 | */ 408 | 409 | public static void frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, 410 | float far) { 411 | if (left == right) { 412 | throw new IllegalArgumentException("left == right"); 413 | } 414 | if (top == bottom) { 415 | throw new IllegalArgumentException("top == bottom"); 416 | } 417 | if (near == far) { 418 | throw new IllegalArgumentException("near == far"); 419 | } 420 | if (near <= 0.0f) { 421 | throw new IllegalArgumentException("near <= 0.0f"); 422 | } 423 | if (far <= 0.0f) { 424 | throw new IllegalArgumentException("far <= 0.0f"); 425 | } 426 | final float r_width = 1.0f / (right - left); 427 | final float r_height = 1.0f / (top - bottom); 428 | final float r_depth = 1.0f / (near - far); 429 | final float x = 2.0f * (near * r_width); 430 | final float y = 2.0f * (near * r_height); 431 | final float A = 2.0f * ((right + left) * r_width); 432 | final float B = (top + bottom) * r_height; 433 | final float C = (far + near) * r_depth; 434 | final float D = 2.0f * (far * near * r_depth); 435 | m[offset + 0] = x; 436 | m[offset + 5] = y; 437 | m[offset + 8] = A; 438 | m[offset + 9] = B; 439 | m[offset + 10] = C; 440 | m[offset + 14] = D; 441 | m[offset + 11] = -1.0f; 442 | m[offset + 1] = 0.0f; 443 | m[offset + 2] = 0.0f; 444 | m[offset + 3] = 0.0f; 445 | m[offset + 4] = 0.0f; 446 | m[offset + 6] = 0.0f; 447 | m[offset + 7] = 0.0f; 448 | m[offset + 12] = 0.0f; 449 | m[offset + 13] = 0.0f; 450 | m[offset + 15] = 0.0f; 451 | } 452 | 453 | /** 454 | * Define a projection matrix in terms of a field of view angle, an 455 | * aspect ratio, and z clip planes 456 | * 457 | * @param m the float array that holds the perspective matrix 458 | * @param offset the offset into float array m where the perspective 459 | * matrix data is written 460 | * @param fovy field of view in y direction, in degrees 461 | * @param aspect width to height aspect ratio of the viewport 462 | * @param zNear 463 | * @param zFar 464 | */ 465 | public static void perspectiveM(float[] m, int offset, float fovy, float aspect, float zNear, float zFar) { 466 | float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0)); 467 | float rangeReciprocal = 1.0f / (zNear - zFar); 468 | 469 | m[offset + 0] = f / aspect; 470 | m[offset + 1] = 0.0f; 471 | m[offset + 2] = 0.0f; 472 | m[offset + 3] = 0.0f; 473 | 474 | m[offset + 4] = 0.0f; 475 | m[offset + 5] = f; 476 | m[offset + 6] = 0.0f; 477 | m[offset + 7] = 0.0f; 478 | 479 | m[offset + 8] = 0.0f; 480 | m[offset + 9] = 0.0f; 481 | m[offset + 10] = (zFar + zNear) * rangeReciprocal; 482 | m[offset + 11] = -1.0f; 483 | 484 | m[offset + 12] = 0.0f; 485 | m[offset + 13] = 0.0f; 486 | m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal; 487 | m[offset + 15] = 0.0f; 488 | } 489 | 490 | /** 491 | * Computes the length of a vector 492 | * 493 | * @param x x coordinate of a vector 494 | * @param y y coordinate of a vector 495 | * @param z z coordinate of a vector 496 | * @return the length of a vector 497 | */ 498 | public static float length(float x, float y, float z) { 499 | return (float) Math.sqrt(x * x + y * y + z * z); 500 | } 501 | 502 | /** 503 | * Sets matrix m to the identity matrix. 504 | * 505 | * @param sm returns the result 506 | * @param smOffset index into sm where the result matrix starts 507 | */ 508 | public static void setIdentityM(float[] sm, int smOffset) { 509 | for (int i = 0; i < 16; i++) { 510 | sm[smOffset + i] = 0; 511 | } 512 | for (int i = 0; i < 16; i += 5) { 513 | sm[smOffset + i] = 1.0f; 514 | } 515 | } 516 | 517 | /** 518 | * Scales matrix m by x, y, and z, putting the result in sm 519 | * 520 | * @param sm returns the result 521 | * @param smOffset index into sm where the result matrix starts 522 | * @param m source matrix 523 | * @param mOffset index into m where the source matrix starts 524 | * @param x scale factor x 525 | * @param y scale factor y 526 | * @param z scale factor z 527 | */ 528 | public static void scaleM(float[] sm, int smOffset, float[] m, int mOffset, float x, float y, float z) { 529 | for (int i = 0; i < 4; i++) { 530 | int smi = smOffset + i; 531 | int mi = mOffset + i; 532 | sm[smi] = m[mi] * x; 533 | sm[4 + smi] = m[4 + mi] * y; 534 | sm[8 + smi] = m[8 + mi] * z; 535 | sm[12 + smi] = m[12 + mi]; 536 | } 537 | } 538 | 539 | /** 540 | * Scales matrix m in place by sx, sy, and sz 541 | * 542 | * @param m matrix to scale 543 | * @param mOffset index into m where the matrix starts 544 | * @param x scale factor x 545 | * @param y scale factor y 546 | * @param z scale factor z 547 | */ 548 | public static void scaleM(float[] m, int mOffset, float x, float y, float z) { 549 | for (int i = 0; i < 4; i++) { 550 | int mi = mOffset + i; 551 | m[mi] *= x; 552 | m[4 + mi] *= y; 553 | m[8 + mi] *= z; 554 | } 555 | } 556 | 557 | /** 558 | * Translates matrix m by x, y, and z, putting the result in tm 559 | * 560 | * @param tm returns the result 561 | * @param tmOffset index into sm where the result matrix starts 562 | * @param m source matrix 563 | * @param mOffset index into m where the source matrix starts 564 | * @param x translation factor x 565 | * @param y translation factor y 566 | * @param z translation factor z 567 | */ 568 | public static void translateM(float[] tm, int tmOffset, float[] m, int mOffset, float x, float y, float z) { 569 | for (int i = 0; i < 12; i++) { 570 | tm[tmOffset + i] = m[mOffset + i]; 571 | } 572 | for (int i = 0; i < 4; i++) { 573 | int tmi = tmOffset + i; 574 | int mi = mOffset + i; 575 | tm[12 + tmi] = m[mi] * x + m[4 + mi] * y + m[8 + mi] * z + m[12 + mi]; 576 | } 577 | } 578 | 579 | /** 580 | * Translates matrix m by x, y, and z in place. 581 | * 582 | * @param m matrix 583 | * @param mOffset index into m where the matrix starts 584 | * @param x translation factor x 585 | * @param y translation factor y 586 | * @param z translation factor z 587 | */ 588 | public static void translateM(float[] m, int mOffset, float x, float y, float z) { 589 | for (int i = 0; i < 4; i++) { 590 | int mi = mOffset + i; 591 | m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z; 592 | } 593 | } 594 | 595 | /** 596 | * Rotates matrix m by angle a (in degrees) around the axis (x, y, z) 597 | * 598 | * @param rm returns the result 599 | * @param rmOffset index into rm where the result matrix starts 600 | * @param m source matrix 601 | * @param mOffset index into m where the source matrix starts 602 | * @param a angle to rotate in degrees 603 | * @param x scale factor x 604 | * @param y scale factor y 605 | * @param z scale factor z 606 | */ 607 | public static void rotateM(float[] rm, int rmOffset, float[] m, int mOffset, float a, float x, float y, float z) { 608 | synchronized (TEMP_MATRIX_ARRAY) { 609 | setRotateM(TEMP_MATRIX_ARRAY, 0, a, x, y, z); 610 | multiplyMM(rm, rmOffset, m, mOffset, TEMP_MATRIX_ARRAY, 0); 611 | } 612 | } 613 | 614 | /** 615 | * Rotates matrix m in place by angle a (in degrees) 616 | * around the axis (x, y, z) 617 | * 618 | * @param m source matrix 619 | * @param mOffset index into m where the matrix starts 620 | * @param a angle to rotate in degrees 621 | * @param x scale factor x 622 | * @param y scale factor y 623 | * @param z scale factor z 624 | */ 625 | public static void rotateM(float[] m, int mOffset, float a, float x, float y, float z) { 626 | synchronized (TEMP_MATRIX_ARRAY) { 627 | setRotateM(TEMP_MATRIX_ARRAY, 0, a, x, y, z); 628 | multiplyMM(TEMP_MATRIX_ARRAY, 16, m, mOffset, TEMP_MATRIX_ARRAY, 0); 629 | System.arraycopy(TEMP_MATRIX_ARRAY, 16, m, mOffset, 16); 630 | } 631 | } 632 | 633 | /** 634 | * Rotates matrix m by angle a (in degrees) around the axis (x, y, z) 635 | * 636 | * @param rm returns the result 637 | * @param rmOffset index into rm where the result matrix starts 638 | * @param a angle to rotate in degrees 639 | * @param x scale factor x 640 | * @param y scale factor y 641 | * @param z scale factor z 642 | */ 643 | public static void setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z) { 644 | rm[rmOffset + 3] = 0; 645 | rm[rmOffset + 7] = 0; 646 | rm[rmOffset + 11] = 0; 647 | rm[rmOffset + 12] = 0; 648 | rm[rmOffset + 13] = 0; 649 | rm[rmOffset + 14] = 0; 650 | rm[rmOffset + 15] = 1; 651 | a *= (float) (Math.PI / 180.0f); 652 | float s = (float) Math.sin(a); 653 | float c = (float) Math.cos(a); 654 | if (1.0f == x && 0.0f == y && 0.0f == z) { 655 | rm[rmOffset + 5] = c; 656 | rm[rmOffset + 10] = c; 657 | rm[rmOffset + 6] = s; 658 | rm[rmOffset + 9] = -s; 659 | rm[rmOffset + 1] = 0; 660 | rm[rmOffset + 2] = 0; 661 | rm[rmOffset + 4] = 0; 662 | rm[rmOffset + 8] = 0; 663 | rm[rmOffset + 0] = 1; 664 | } else if (0.0f == x && 1.0f == y && 0.0f == z) { 665 | rm[rmOffset + 0] = c; 666 | rm[rmOffset + 10] = c; 667 | rm[rmOffset + 8] = s; 668 | rm[rmOffset + 2] = -s; 669 | rm[rmOffset + 1] = 0; 670 | rm[rmOffset + 4] = 0; 671 | rm[rmOffset + 6] = 0; 672 | rm[rmOffset + 9] = 0; 673 | rm[rmOffset + 5] = 1; 674 | } else if (0.0f == x && 0.0f == y && 1.0f == z) { 675 | rm[rmOffset + 0] = c; 676 | rm[rmOffset + 5] = c; 677 | rm[rmOffset + 1] = s; 678 | rm[rmOffset + 4] = -s; 679 | rm[rmOffset + 2] = 0; 680 | rm[rmOffset + 6] = 0; 681 | rm[rmOffset + 8] = 0; 682 | rm[rmOffset + 9] = 0; 683 | rm[rmOffset + 10] = 1; 684 | } else { 685 | float len = length(x, y, z); 686 | if (1.0f != len) { 687 | float recipLen = 1.0f / len; 688 | x *= recipLen; 689 | y *= recipLen; 690 | z *= recipLen; 691 | } 692 | float nc = 1.0f - c; 693 | float xy = x * y; 694 | float yz = y * z; 695 | float zx = z * x; 696 | float xs = x * s; 697 | float ys = y * s; 698 | float zs = z * s; 699 | rm[rmOffset + 0] = x * x * nc + c; 700 | rm[rmOffset + 4] = xy * nc - zs; 701 | rm[rmOffset + 8] = zx * nc + ys; 702 | rm[rmOffset + 1] = xy * nc + zs; 703 | rm[rmOffset + 5] = y * y * nc + c; 704 | rm[rmOffset + 9] = yz * nc - xs; 705 | rm[rmOffset + 2] = zx * nc - ys; 706 | rm[rmOffset + 6] = yz * nc + xs; 707 | rm[rmOffset + 10] = z * z * nc + c; 708 | } 709 | } 710 | 711 | /** 712 | * Converts Euler angles to a rotation matrix 713 | * 714 | * @param rm returns the result 715 | * @param rmOffset index into rm where the result matrix starts 716 | * @param x angle of rotation, in degrees 717 | * @param y angle of rotation, in degrees 718 | * @param z angle of rotation, in degrees 719 | */ 720 | public static void setRotateEulerM(float[] rm, int rmOffset, float x, float y, float z) { 721 | x *= (float) (Math.PI / 180.0f); 722 | y *= (float) (Math.PI / 180.0f); 723 | z *= (float) (Math.PI / 180.0f); 724 | float cx = (float) Math.cos(x); 725 | float sx = (float) Math.sin(x); 726 | float cy = (float) Math.cos(y); 727 | float sy = (float) Math.sin(y); 728 | float cz = (float) Math.cos(z); 729 | float sz = (float) Math.sin(z); 730 | float cxsy = cx * sy; 731 | float sxsy = sx * sy; 732 | 733 | rm[rmOffset + 0] = cy * cz; 734 | rm[rmOffset + 1] = -cy * sz; 735 | rm[rmOffset + 2] = sy; 736 | rm[rmOffset + 3] = 0.0f; 737 | 738 | rm[rmOffset + 4] = cxsy * cz + cx * sz; 739 | rm[rmOffset + 5] = -cxsy * sz + cx * cz; 740 | rm[rmOffset + 6] = -sx * cy; 741 | rm[rmOffset + 7] = 0.0f; 742 | 743 | rm[rmOffset + 8] = -sxsy * cz + sx * sz; 744 | rm[rmOffset + 9] = sxsy * sz + sx * cz; 745 | rm[rmOffset + 10] = cx * cy; 746 | rm[rmOffset + 11] = 0.0f; 747 | 748 | rm[rmOffset + 12] = 0.0f; 749 | rm[rmOffset + 13] = 0.0f; 750 | rm[rmOffset + 14] = 0.0f; 751 | rm[rmOffset + 15] = 1.0f; 752 | } 753 | 754 | /** 755 | * Define a viewing transformation in terms of an eye point, a center of 756 | * view, and an up vector. 757 | * 758 | * @param rm returns the result 759 | * @param rmOffset index into rm where the result matrix starts 760 | * @param eyeX eye point X 761 | * @param eyeY eye point Y 762 | * @param eyeZ eye point Z 763 | * @param centerX center of view X 764 | * @param centerY center of view Y 765 | * @param centerZ center of view Z 766 | * @param upX up vector X 767 | * @param upY up vector Y 768 | * @param upZ up vector Z 769 | */ 770 | public static void setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, 771 | float centerY, float centerZ, float upX, float upY, float upZ) { 772 | 773 | // See the OpenGL GLUT documentation for gluLookAt for a description 774 | // of the algorithm. We implement it in a straightforward way: 775 | 776 | float fx = centerX - eyeX; 777 | float fy = centerY - eyeY; 778 | float fz = centerZ - eyeZ; 779 | 780 | // Normalize f 781 | float rlf = 1.0f / Matrix.length(fx, fy, fz); 782 | fx *= rlf; 783 | fy *= rlf; 784 | fz *= rlf; 785 | 786 | // compute s = f x up (x means "cross product") 787 | float sx = fy * upZ - fz * upY; 788 | float sy = fz * upX - fx * upZ; 789 | float sz = fx * upY - fy * upX; 790 | 791 | // and normalize s 792 | float rls = 1.0f / Matrix.length(sx, sy, sz); 793 | sx *= rls; 794 | sy *= rls; 795 | sz *= rls; 796 | 797 | // compute u = s x f 798 | float ux = sy * fz - sz * fy; 799 | float uy = sz * fx - sx * fz; 800 | float uz = sx * fy - sy * fx; 801 | 802 | rm[rmOffset + 0] = sx; 803 | rm[rmOffset + 1] = ux; 804 | rm[rmOffset + 2] = -fx; 805 | rm[rmOffset + 3] = 0.0f; 806 | 807 | rm[rmOffset + 4] = sy; 808 | rm[rmOffset + 5] = uy; 809 | rm[rmOffset + 6] = -fy; 810 | rm[rmOffset + 7] = 0.0f; 811 | 812 | rm[rmOffset + 8] = sz; 813 | rm[rmOffset + 9] = uz; 814 | rm[rmOffset + 10] = -fz; 815 | rm[rmOffset + 11] = 0.0f; 816 | 817 | rm[rmOffset + 12] = 0.0f; 818 | rm[rmOffset + 13] = 0.0f; 819 | rm[rmOffset + 14] = 0.0f; 820 | rm[rmOffset + 15] = 1.0f; 821 | 822 | translateM(rm, rmOffset, -eyeX, -eyeY, -eyeZ); 823 | } 824 | } -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/representation/MatrixF4x4.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.representation; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * The Class MatrixF4x4. 7 | * 8 | * Internal the matrix is structured as 9 | * 10 | * [ x0 , y0 , z0 , w0 ] [ x1 , y1 , z1 , w1 ] [ x2 , y2 , z2 , w2 ] [ x3 , y3 , z3 , w3 ] 11 | * 12 | * it is recommend that when setting the matrix values individually that you use the set{x,#} methods, where 'x' is 13 | * either x, y, z or w and # is either 0, 1, 2 or 3, setY1 for example. The reason you should use these functions is 14 | * because it will map directly to that part of the matrix regardless of whether or not the internal matrix is column 15 | * major or not. If the matrix is either or length 9 or 16 it will be able to determine if it can set the value or not. 16 | * If the matrix is of size 9 but you set say w2, the value will not be set and the set method will return without any 17 | * error. 18 | * 19 | */ 20 | public class MatrixF4x4 { 21 | 22 | public static final int[] matIndCol9_3x3 = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; 23 | public static final int[] matIndCol16_3x3 = { 0, 1, 2, 4, 5, 6, 8, 9, 10 }; 24 | public static final int[] matIndRow9_3x3 = { 0, 3, 6, 1, 4, 7, 3, 5, 8 }; 25 | public static final int[] matIndRow16_3x3 = { 0, 4, 8, 1, 5, 9, 2, 6, 10 }; 26 | 27 | public static final int[] matIndCol16_4x4 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; 28 | public static final int[] matIndRow16_4x4 = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; 29 | 30 | private boolean colMaj = true; 31 | 32 | /** The matrix. */ 33 | public float[] matrix; 34 | 35 | /** 36 | * Instantiates a new matrixf4x4. The Matrix is assumed to be Column major, however you can change this by using the 37 | * setColumnMajor function to false and it will operate like a row major matrix. 38 | */ 39 | public MatrixF4x4() { 40 | // The matrix is defined as float[column][row] 41 | this.matrix = new float[16]; 42 | Matrix.setIdentityM(this.matrix, 0); 43 | } 44 | 45 | /** 46 | * Gets the matrix. 47 | * 48 | * @return the matrix, can be null if the matrix is invalid 49 | */ 50 | public float[] getMatrix() { 51 | return this.matrix; 52 | } 53 | 54 | public int size() { 55 | return matrix.length; 56 | } 57 | 58 | /** 59 | * Sets the matrix from a float[16] array. If the matrix you set isn't 16 long then the matrix will be set as 60 | * invalid. 61 | * 62 | * @param matrix the new matrix 63 | */ 64 | public void setMatrix(float[] matrix) { 65 | if (matrix.length == 16 || matrix.length == 9) 66 | this.matrix = matrix; 67 | else { 68 | throw new IllegalArgumentException("Matrix set is invalid, size is " + matrix.length + " expected 9 or 16"); 69 | } 70 | } 71 | 72 | public void set(MatrixF4x4 source) { 73 | System.arraycopy(source.matrix, 0, matrix, 0, matrix.length); 74 | } 75 | 76 | /** 77 | * Set whether the internal data is col major by passing true, or false for a row major matrix. The matrix is column 78 | * major by default. 79 | * 80 | * @param colMajor 81 | */ 82 | public void setColumnMajor(boolean colMajor) { 83 | this.colMaj = colMajor; 84 | } 85 | 86 | /** 87 | * Find out if the stored matrix is column major 88 | * 89 | * @return 90 | */ 91 | public boolean isColumnMajor() { 92 | return colMaj; 93 | } 94 | 95 | /** 96 | * Multiply the given vector by this matrix. This should only be used if the matrix is of size 16 (use the 97 | * matrix.size() method). 98 | * 99 | * @param vector A vector of length 4. 100 | */ 101 | public void multiplyVector4fByMatrix(Vector4f vector) { 102 | 103 | if (matrix.length == 16) { 104 | float x = 0; 105 | float y = 0; 106 | float z = 0; 107 | float w = 0; 108 | 109 | float[] vectorArray = vector.array(); 110 | 111 | if (colMaj) { 112 | for (int i = 0; i < 4; i++) { 113 | 114 | int k = i * 4; 115 | 116 | x += this.matrix[k + 0] * vectorArray[i]; 117 | y += this.matrix[k + 1] * vectorArray[i]; 118 | z += this.matrix[k + 2] * vectorArray[i]; 119 | w += this.matrix[k + 3] * vectorArray[i]; 120 | } 121 | } else { 122 | for (int i = 0; i < 4; i++) { 123 | 124 | x += this.matrix[0 + i] * vectorArray[i]; 125 | y += this.matrix[4 + i] * vectorArray[i]; 126 | z += this.matrix[8 + i] * vectorArray[i]; 127 | w += this.matrix[12 + i] * vectorArray[i]; 128 | } 129 | } 130 | 131 | vector.setX(x); 132 | vector.setY(y); 133 | vector.setZ(z); 134 | vector.setW(w); 135 | } else 136 | Log.e("matrix", "Matrix is invalid, is " + matrix.length + " long, this equation expects a 16 value matrix"); 137 | } 138 | 139 | /** 140 | * Multiply the given vector by this matrix. This should only be used if the matrix is of size 9 (use the 141 | * matrix.size() method). 142 | * 143 | * @param vector A vector of length 3. 144 | */ 145 | public void multiplyVector3fByMatrix(Vector3f vector) { 146 | 147 | if (matrix.length == 9) { 148 | float x = 0; 149 | float y = 0; 150 | float z = 0; 151 | 152 | float[] vectorArray = vector.toArray(); 153 | 154 | if (!colMaj) { 155 | for (int i = 0; i < 3; i++) { 156 | 157 | int k = i * 3; 158 | 159 | x += this.matrix[k + 0] * vectorArray[i]; 160 | y += this.matrix[k + 1] * vectorArray[i]; 161 | z += this.matrix[k + 2] * vectorArray[i]; 162 | } 163 | } else { 164 | for (int i = 0; i < 3; i++) { 165 | 166 | x += this.matrix[0 + i] * vectorArray[i]; 167 | y += this.matrix[3 + i] * vectorArray[i]; 168 | z += this.matrix[6 + i] * vectorArray[i]; 169 | } 170 | } 171 | 172 | vector.setX(x); 173 | vector.setY(y); 174 | vector.setZ(z); 175 | } else 176 | Log.e("matrix", "Matrix is invalid, is " + matrix.length 177 | + " long, this function expects the internal matrix to be of size 9"); 178 | } 179 | 180 | /** 181 | * Multiply matrix4x4 by matrix. 182 | * 183 | * @param matrixf the matrixf 184 | */ 185 | public void multiplyMatrix4x4ByMatrix(MatrixF4x4 matrixf) { 186 | 187 | // TODO implement Strassen Algorithm in place of this slower naive one. 188 | float[] bufferMatrix = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 189 | float[] matrix = matrixf.getMatrix(); 190 | 191 | /** 192 | * for(int i = 0; i < 4; i++){ for(int j = 0; j < 4; j++){ 193 | * 194 | * int k = i * 4; bufferMatrix[0 + j] += this.matrix[k + j] * matrix[0 * 4 + i]; bufferMatrix[1 * 4 + j] += 195 | * this.matrix[k + j] * matrix[1 * 4 + i]; bufferMatrix[2 * 4 + j] += this.matrix[k + j] * matrix[2 * 4 + 196 | * i]; bufferMatrix[3 * 4 + j] += this.matrix[k + j] * matrix[3 * 4 + i]; } } 197 | */ 198 | 199 | multiplyMatrix(matrix, 0, bufferMatrix, 0); 200 | matrixf.setMatrix(bufferMatrix); 201 | } 202 | 203 | public void multiplyMatrix(float[] input, int inputOffset, float[] output, int outputOffset) { 204 | float[] bufferMatrix = output; 205 | float[] matrix = input; 206 | 207 | for (int i = 0; i < 4; i++) { 208 | for (int j = 0; j < 4; j++) { 209 | 210 | int k = i * 4; 211 | bufferMatrix[outputOffset + 0 + j] += this.matrix[k + j] * matrix[inputOffset + 0 * 4 + i]; 212 | bufferMatrix[outputOffset + 1 * 4 + j] += this.matrix[k + j] * matrix[inputOffset + 1 * 4 + i]; 213 | bufferMatrix[outputOffset + 2 * 4 + j] += this.matrix[k + j] * matrix[inputOffset + 2 * 4 + i]; 214 | bufferMatrix[outputOffset + 3 * 4 + j] += this.matrix[k + j] * matrix[inputOffset + 3 * 4 + i]; 215 | } 216 | } 217 | } 218 | 219 | /** 220 | * This will rearrange the internal structure of the matrix. Be careful though as this is an expensive operation. 221 | */ 222 | public void transpose() { 223 | if (this.matrix.length == 16) { 224 | float[] newMatrix = new float[16]; 225 | for (int i = 0; i < 4; i++) { 226 | 227 | int k = i * 4; 228 | 229 | newMatrix[k] = matrix[i]; 230 | newMatrix[k + 1] = matrix[4 + i]; 231 | newMatrix[k + 2] = matrix[8 + i]; 232 | newMatrix[k + 3] = matrix[12 + i]; 233 | } 234 | matrix = newMatrix; 235 | 236 | } else { 237 | float[] newMatrix = new float[9]; 238 | for (int i = 0; i < 3; i++) { 239 | 240 | int k = i * 3; 241 | 242 | newMatrix[k] = matrix[i]; 243 | newMatrix[k + 1] = matrix[3 + i]; 244 | newMatrix[k + 2] = matrix[6 + i]; 245 | } 246 | matrix = newMatrix; 247 | } 248 | 249 | } 250 | 251 | public void setX0(float value) { 252 | 253 | if (matrix.length == 16) { 254 | if (colMaj) 255 | matrix[matIndCol16_3x3[0]] = value; 256 | else 257 | matrix[matIndRow16_3x3[0]] = value; 258 | } else { 259 | if (colMaj) 260 | matrix[matIndCol9_3x3[0]] = value; 261 | else 262 | matrix[matIndRow9_3x3[0]] = value; 263 | } 264 | } 265 | 266 | public void setX1(float value) { 267 | 268 | if (matrix.length == 16) { 269 | if (colMaj) 270 | matrix[matIndCol16_3x3[1]] = value; 271 | else 272 | matrix[matIndRow16_3x3[1]] = value; 273 | } else { 274 | if (colMaj) 275 | matrix[matIndCol9_3x3[1]] = value; 276 | else 277 | matrix[matIndRow9_3x3[1]] = value; 278 | } 279 | } 280 | 281 | public void setX2(float value) { 282 | 283 | if (matrix.length == 16) { 284 | if (colMaj) 285 | matrix[matIndCol16_3x3[2]] = value; 286 | else 287 | matrix[matIndRow16_3x3[2]] = value; 288 | } else { 289 | if (colMaj) 290 | matrix[matIndCol9_3x3[2]] = value; 291 | else 292 | matrix[matIndRow9_3x3[2]] = value; 293 | } 294 | } 295 | 296 | public void setY0(float value) { 297 | 298 | if (matrix.length == 16) { 299 | if (colMaj) 300 | matrix[matIndCol16_3x3[3]] = value; 301 | else 302 | matrix[matIndRow16_3x3[3]] = value; 303 | } else { 304 | if (colMaj) 305 | matrix[matIndCol9_3x3[3]] = value; 306 | else 307 | matrix[matIndRow9_3x3[3]] = value; 308 | } 309 | } 310 | 311 | public void setY1(float value) { 312 | 313 | if (matrix.length == 16) { 314 | if (colMaj) 315 | matrix[matIndCol16_3x3[4]] = value; 316 | else 317 | matrix[matIndRow16_3x3[4]] = value; 318 | } else { 319 | if (colMaj) 320 | matrix[matIndCol9_3x3[4]] = value; 321 | else 322 | matrix[matIndRow9_3x3[4]] = value; 323 | } 324 | } 325 | 326 | public void setY2(float value) { 327 | 328 | if (matrix.length == 16) { 329 | if (colMaj) 330 | matrix[matIndCol16_3x3[5]] = value; 331 | else 332 | matrix[matIndRow16_3x3[5]] = value; 333 | } else { 334 | if (colMaj) 335 | matrix[matIndCol9_3x3[5]] = value; 336 | else 337 | matrix[matIndRow9_3x3[5]] = value; 338 | } 339 | } 340 | 341 | public void setZ0(float value) { 342 | 343 | if (matrix.length == 16) { 344 | if (colMaj) 345 | matrix[matIndCol16_3x3[6]] = value; 346 | else 347 | matrix[matIndRow16_3x3[6]] = value; 348 | } else { 349 | if (colMaj) 350 | matrix[matIndCol9_3x3[6]] = value; 351 | else 352 | matrix[matIndRow9_3x3[6]] = value; 353 | } 354 | } 355 | 356 | public void setZ1(float value) { 357 | 358 | if (matrix.length == 16) { 359 | if (colMaj) 360 | matrix[matIndCol16_3x3[7]] = value; 361 | else 362 | matrix[matIndRow16_3x3[7]] = value; 363 | } else { 364 | if (colMaj) 365 | matrix[matIndCol9_3x3[7]] = value; 366 | else 367 | matrix[matIndRow9_3x3[7]] = value; 368 | } 369 | } 370 | 371 | public void setZ2(float value) { 372 | 373 | if (matrix.length == 16) { 374 | if (colMaj) 375 | matrix[matIndCol16_3x3[8]] = value; 376 | else 377 | matrix[matIndRow16_3x3[8]] = value; 378 | } else { 379 | if (colMaj) 380 | matrix[matIndCol9_3x3[8]] = value; 381 | else 382 | matrix[matIndRow9_3x3[8]] = value; 383 | } 384 | } 385 | 386 | public void setX3(float value) { 387 | 388 | if (matrix.length == 16) { 389 | if (colMaj) 390 | matrix[matIndCol16_4x4[3]] = value; 391 | else 392 | matrix[matIndRow16_4x4[3]] = value; 393 | }else 394 | throw new IllegalStateException("length of matrix should be 16"); 395 | } 396 | 397 | public void setY3(float value) { 398 | 399 | if (matrix.length == 16) { 400 | if (colMaj) 401 | matrix[matIndCol16_4x4[7]] = value; 402 | else 403 | matrix[matIndRow16_4x4[7]] = value; 404 | }else 405 | throw new IllegalStateException("length of matrix should be 16"); 406 | } 407 | 408 | public void setZ3(float value) { 409 | 410 | if (matrix.length == 16) { 411 | if (colMaj) 412 | matrix[matIndCol16_4x4[11]] = value; 413 | else 414 | matrix[matIndRow16_4x4[11]] = value; 415 | }else 416 | throw new IllegalStateException("length of matrix should be 16"); 417 | } 418 | 419 | public void setW0(float value) { 420 | 421 | if (matrix.length == 16) { 422 | if (colMaj) 423 | matrix[matIndCol16_4x4[12]] = value; 424 | else 425 | matrix[matIndRow16_4x4[12]] = value; 426 | }else 427 | throw new IllegalStateException("length of matrix should be 16"); 428 | } 429 | 430 | public void setW1(float value) { 431 | 432 | if (matrix.length == 16) { 433 | if (colMaj) 434 | matrix[matIndCol16_4x4[13]] = value; 435 | else 436 | matrix[matIndRow16_4x4[13]] = value; 437 | }else 438 | throw new IllegalStateException("length of matrix should be 16"); 439 | } 440 | 441 | public void setW2(float value) { 442 | 443 | if (matrix.length == 16) { 444 | if (colMaj) 445 | matrix[matIndCol16_4x4[14]] = value; 446 | else 447 | matrix[matIndRow16_4x4[14]] = value; 448 | }else 449 | throw new IllegalStateException("length of matrix should be 16"); 450 | } 451 | 452 | public void setW3(float value) { 453 | 454 | if (matrix.length == 16) { 455 | if (colMaj) 456 | matrix[matIndCol16_4x4[15]] = value; 457 | else 458 | matrix[matIndRow16_4x4[15]] = value; 459 | }else 460 | throw new IllegalStateException("length of matrix should be 16"); 461 | } 462 | 463 | } 464 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/representation/Quaternion.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.representation; 2 | 3 | /** 4 | * The Quaternion class. A Quaternion is a four-dimensional vector that is used to represent rotations of a rigid body 5 | * in the 3D space. It is very similar to a rotation vector; it contains an angle, encoded into the w component 6 | * and three components to describe the rotation-axis (encoded into x, y, z). 7 | * 8 | *

9 | * Quaternions allow for elegant descriptions of 3D rotations, interpolations as well as extrapolations and compared to 10 | * Euler angles, they don't suffer from gimbal lock. Interpolations between two Quaternions are called SLERP (Spherical 11 | * Linear Interpolation). 12 | *

13 | * 14 | *

15 | * This class also contains the representation of the same rotation as a Quaternion and 4x4-Rotation-Matrix. 16 | *

17 | * 18 | * @author Leigh Beattie, Alexander Pacha 19 | * 20 | */ 21 | public class Quaternion extends Vector4f { 22 | 23 | /** 24 | * Rotation matrix that contains the same rotation as the Quaternion in a 4x4 homogenised rotation matrix. 25 | * Remember that for performance reasons, this matrix is only updated, when it is accessed and not on every change 26 | * of the quaternion-values. 27 | */ 28 | private MatrixF4x4 matrix; 29 | 30 | /** 31 | * This variable is used to synchronise the rotation matrix with the current quaternion values. If someone has 32 | * changed the 33 | * quaternion numbers then the matrix will need to be updated. To save on processing we only really want to update 34 | * the matrix when someone wants to fetch it, instead of whenever someone sets a quaternion value. 35 | */ 36 | private boolean dirty = false; 37 | 38 | private Vector4f tmpVector = new Vector4f(); 39 | private Quaternion tmpQuaternion; 40 | 41 | /** 42 | * Creates a new Quaternion object and initialises it with the identity Quaternion 43 | */ 44 | public Quaternion() { 45 | super(); 46 | matrix = new MatrixF4x4(); 47 | loadIdentityQuat(); 48 | } 49 | 50 | /** 51 | * Normalise this Quaternion into a unity Quaternion. 52 | */ 53 | public void normalise() { 54 | this.dirty = true; 55 | float mag = (float) Math.sqrt(points[3] * points[3] + points[0] * points[0] + points[1] * points[1] + points[2] 56 | * points[2]); 57 | points[3] = points[3] / mag; 58 | points[0] = points[0] / mag; 59 | points[1] = points[1] / mag; 60 | points[2] = points[2] / mag; 61 | } 62 | 63 | @Override 64 | public void normalize() { 65 | normalise(); 66 | } 67 | 68 | /** 69 | * Copies the values from the given quaternion to this one 70 | * 71 | * @param quat The quaternion to copy from 72 | */ 73 | public void set(Quaternion quat) { 74 | this.dirty = true; 75 | copyVec4(quat); 76 | } 77 | 78 | /** 79 | * Multiply this quaternion by the input quaternion and store the result in the out quaternion 80 | * 81 | * @param input 82 | * @param output 83 | */ 84 | public void multiplyByQuat(Quaternion input, Quaternion output) { 85 | 86 | if (input != output) { 87 | output.points[3] = (points[3] * input.points[3] - points[0] * input.points[0] - points[1] * input.points[1] - points[2] 88 | * input.points[2]); //w = w1w2 - x1x2 - y1y2 - z1z2 89 | output.points[0] = (points[3] * input.points[0] + points[0] * input.points[3] + points[1] * input.points[2] - points[2] 90 | * input.points[1]); //x = w1x2 + x1w2 + y1z2 - z1y2 91 | output.points[1] = (points[3] * input.points[1] + points[1] * input.points[3] + points[2] * input.points[0] - points[0] 92 | * input.points[2]); //y = w1y2 + y1w2 + z1x2 - x1z2 93 | output.points[2] = (points[3] * input.points[2] + points[2] * input.points[3] + points[0] * input.points[1] - points[1] 94 | * input.points[0]); //z = w1z2 + z1w2 + x1y2 - y1x2 95 | } else { 96 | tmpVector.points[0] = input.points[0]; 97 | tmpVector.points[1] = input.points[1]; 98 | tmpVector.points[2] = input.points[2]; 99 | tmpVector.points[3] = input.points[3]; 100 | 101 | output.points[3] = (points[3] * tmpVector.points[3] - points[0] * tmpVector.points[0] - points[1] 102 | * tmpVector.points[1] - points[2] * tmpVector.points[2]); //w = w1w2 - x1x2 - y1y2 - z1z2 103 | output.points[0] = (points[3] * tmpVector.points[0] + points[0] * tmpVector.points[3] + points[1] 104 | * tmpVector.points[2] - points[2] * tmpVector.points[1]); //x = w1x2 + x1w2 + y1z2 - z1y2 105 | output.points[1] = (points[3] * tmpVector.points[1] + points[1] * tmpVector.points[3] + points[2] 106 | * tmpVector.points[0] - points[0] * tmpVector.points[2]); //y = w1y2 + y1w2 + z1x2 - x1z2 107 | output.points[2] = (points[3] * tmpVector.points[2] + points[2] * tmpVector.points[3] + points[0] 108 | * tmpVector.points[1] - points[1] * tmpVector.points[0]); //z = w1z2 + z1w2 + x1y2 - y1x2 109 | } 110 | } 111 | 112 | /** 113 | * Multiply this quaternion by the input quaternion and store the result in the out quaternion 114 | * 115 | * @param input 116 | */ 117 | public void multiplyByQuat(Quaternion input) { 118 | this.dirty = true; 119 | if(tmpQuaternion == null) tmpQuaternion = new Quaternion(); 120 | tmpQuaternion.copyVec4(this); 121 | multiplyByQuat(input, tmpQuaternion); 122 | this.copyVec4(tmpQuaternion); 123 | } 124 | 125 | /** 126 | * Multiplies this Quaternion with a scalar 127 | * 128 | * @param scalar the value that the vector should be multiplied with 129 | */ 130 | public void multiplyByScalar(float scalar) { 131 | this.dirty = true; 132 | multiplyByScalar(scalar); 133 | } 134 | 135 | /** 136 | * Add a quaternion to this quaternion 137 | * 138 | * @param input The quaternion that you want to add to this one 139 | */ 140 | public void addQuat(Quaternion input) { 141 | this.dirty = true; 142 | addQuat(input, this); 143 | } 144 | 145 | /** 146 | * Add this quaternion and another quaternion together and store the result in the output quaternion 147 | * 148 | * @param input The quaternion you want added to this quaternion 149 | * @param output The quaternion you want to store the output in. 150 | */ 151 | public void addQuat(Quaternion input, Quaternion output) { 152 | output.setX(getX() + input.getX()); 153 | output.setY(getY() + input.getY()); 154 | output.setZ(getZ() + input.getZ()); 155 | output.setW(getW() + input.getW()); 156 | } 157 | 158 | /** 159 | * Subtract a quaternion to this quaternion 160 | * 161 | * @param input The quaternion that you want to subtracted from this one 162 | */ 163 | public void subQuat(Quaternion input) { 164 | this.dirty = true; 165 | subQuat(input, this); 166 | } 167 | 168 | /** 169 | * Subtract another quaternion from this quaternion and store the result in the output quaternion 170 | * 171 | * @param input The quaternion you want subtracted from this quaternion 172 | * @param output The quaternion you want to store the output in. 173 | */ 174 | public void subQuat(Quaternion input, Quaternion output) { 175 | output.setX(getX() - input.getX()); 176 | output.setY(getY() - input.getY()); 177 | output.setZ(getZ() - input.getZ()); 178 | output.setW(getW() - input.getW()); 179 | } 180 | 181 | /** 182 | * Converts this Quaternion into the Rotation-Matrix representation which can be accessed by 183 | * {@link Quaternion#getMatrix4x4 getMatrix4x4} 184 | */ 185 | private void convertQuatToMatrix() { 186 | float x = points[0]; 187 | float y = points[1]; 188 | float z = points[2]; 189 | float w = points[3]; 190 | 191 | matrix.setX0(1 - 2 * (y * y) - 2 * (z * z)); //1 - 2y2 - 2z2 192 | matrix.setX1(2 * (x * y) + 2 * (w * z)); // 2xy - 2wz 193 | matrix.setX2(2 * (x * z) - 2 * (w * y)); //2xz + 2wy 194 | matrix.setX3(0); 195 | matrix.setY0(2 * (x * y) - 2 * (w * z)); //2xy + 2wz 196 | matrix.setY1(1 - 2 * (x * x) - 2 * (z * z)); //1 - 2x2 - 2z2 197 | matrix.setY2(2 * (y * z) + 2 * (w * x)); // 2yz + 2wx 198 | matrix.setY3(0); 199 | matrix.setZ0(2 * (x * z) + 2 * (w * y)); //2xz + 2wy 200 | matrix.setZ1(2 * (y * z) - 2 * (w * x)); //2yz - 2wx 201 | matrix.setZ2(1 - 2 * (x * x) - 2 * (y * y)); //1 - 2x2 - 2y2 202 | matrix.setZ3(0); 203 | matrix.setW0(0); 204 | matrix.setW1(0); 205 | matrix.setW2(0); 206 | matrix.setW3(1); 207 | } 208 | 209 | /** 210 | * Get an axis angle representation of this quaternion. 211 | * 212 | * @param output Vector4f axis angle. 213 | */ 214 | public void toAxisAngle(Vector4f output) { 215 | if (getW() > 1) { 216 | normalise(); // if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised 217 | } 218 | float angle = 2 * (float) Math.toDegrees(Math.acos(getW())); 219 | float x; 220 | float y; 221 | float z; 222 | 223 | float s = (float) Math.sqrt(1 - getW() * getW()); // assuming quaternion normalised then w is less than 1, so term always positive. 224 | if (s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt 225 | // if s close to zero then direction of axis not important 226 | x = points[0]; // if it is important that axis is normalised then replace with x=1; y=z=0; 227 | y = points[1]; 228 | z = points[2]; 229 | } else { 230 | x = points[0] / s; // normalise axis 231 | y = points[1] / s; 232 | z = points[2] / s; 233 | } 234 | 235 | output.points[0] = x; 236 | output.points[1] = y; 237 | output.points[2] = z; 238 | output.points[3] = angle; 239 | } 240 | 241 | /** 242 | * Returns the heading, attitude and bank of this quaternion as euler angles in the double array respectively 243 | * 244 | * @return An array of size 3 containing the euler angles for this quaternion 245 | */ 246 | public double[] toEulerAngles() { 247 | double[] ret = new double[3]; 248 | 249 | ret[0] = Math.atan2(2 * points[1] * getW() - 2 * points[0] * points[2], 1 - 2 * (points[1] * points[1]) - 2 250 | * (points[2] * points[2])); // atan2(2*qy*qw-2*qx*qz , 1 - 2*qy2 - 2*qz2) 251 | ret[1] = Math.asin(2 * points[0] * points[1] + 2 * points[2] * getW()); // asin(2*qx*qy + 2*qz*qw) 252 | ret[2] = Math.atan2(2 * points[0] * getW() - 2 * points[1] * points[2], 1 - 2 * (points[0] * points[0]) - 2 253 | * (points[2] * points[2])); // atan2(2*qx*qw-2*qy*qz , 1 - 2*qx2 - 2*qz2) 254 | 255 | return ret; 256 | } 257 | 258 | /** 259 | * Sets the quaternion to an identity quaternion of 0,0,0,1. 260 | */ 261 | public void loadIdentityQuat() { 262 | this.dirty = true; 263 | setX(0); 264 | setY(0); 265 | setZ(0); 266 | setW(1); 267 | } 268 | 269 | @Override 270 | public String toString() { 271 | return "{X: " + getX() + ", Y:" + getY() + ", Z:" + getZ() + ", W:" + getW() + "}"; 272 | } 273 | 274 | /** 275 | * This is an internal method used to build a quaternion from a rotation matrix and then sets the current quaternion 276 | * from that matrix. 277 | * 278 | */ 279 | private void generateQuaternionFromMatrix() { 280 | 281 | float qx; 282 | float qy; 283 | float qz; 284 | float qw; 285 | 286 | float[] mat = matrix.getMatrix(); 287 | int[] indices = null; 288 | 289 | if (this.matrix.size() == 16) { 290 | if (this.matrix.isColumnMajor()) { 291 | indices = MatrixF4x4.matIndCol16_3x3; 292 | } else { 293 | indices = MatrixF4x4.matIndRow16_3x3; 294 | } 295 | } else { 296 | if (this.matrix.isColumnMajor()) { 297 | indices = MatrixF4x4.matIndCol9_3x3; 298 | } else { 299 | indices = MatrixF4x4.matIndRow9_3x3; 300 | } 301 | } 302 | 303 | int m00 = indices[0]; 304 | int m01 = indices[1]; 305 | int m02 = indices[2]; 306 | 307 | int m10 = indices[3]; 308 | int m11 = indices[4]; 309 | int m12 = indices[5]; 310 | 311 | int m20 = indices[6]; 312 | int m21 = indices[7]; 313 | int m22 = indices[8]; 314 | 315 | float tr = mat[m00] + mat[m11] + mat[m22]; 316 | if (tr > 0) { 317 | float s = (float) Math.sqrt(tr + 1.0) * 2; // S=4*qw 318 | qw = 0.25f * s; 319 | qx = (mat[m21] - mat[m12]) / s; 320 | qy = (mat[m02] - mat[m20]) / s; 321 | qz = (mat[m10] - mat[m01]) / s; 322 | } else if ((mat[m00] > mat[m11]) & (mat[m00] > mat[m22])) { 323 | float s = (float) Math.sqrt(1.0 + mat[m00] - mat[m11] - mat[m22]) * 2; // S=4*qx 324 | qw = (mat[m21] - mat[m12]) / s; 325 | qx = 0.25f * s; 326 | qy = (mat[m01] + mat[m10]) / s; 327 | qz = (mat[m02] + mat[m20]) / s; 328 | } else if (mat[m11] > mat[m22]) { 329 | float s = (float) Math.sqrt(1.0 + mat[m11] - mat[m00] - mat[m22]) * 2; // S=4*qy 330 | qw = (mat[m02] - mat[m20]) / s; 331 | qx = (mat[m01] + mat[m10]) / s; 332 | qy = 0.25f * s; 333 | qz = (mat[m12] + mat[m21]) / s; 334 | } else { 335 | float s = (float) Math.sqrt(1.0 + mat[m22] - mat[m00] - mat[m11]) * 2; // S=4*qz 336 | qw = (mat[m10] - mat[m01]) / s; 337 | qx = (mat[m02] + mat[m20]) / s; 338 | qy = (mat[m12] + mat[m21]) / s; 339 | qz = 0.25f * s; 340 | } 341 | 342 | setX(qx); 343 | setY(qy); 344 | setZ(qz); 345 | setW(qw); 346 | } 347 | 348 | /** 349 | * You can set the values for this quaternion based off a rotation matrix. If the matrix you supply is not a 350 | * rotation matrix this will fail. You MUST provide a 4x4 matrix. 351 | * 352 | * @param matrix A column major rotation matrix 353 | */ 354 | public void setColumnMajor(float[] matrix) { 355 | 356 | this.matrix.setMatrix(matrix); 357 | this.matrix.setColumnMajor(true); 358 | 359 | generateQuaternionFromMatrix(); 360 | } 361 | 362 | /** 363 | * You can set the values for this quaternion based off a rotation matrix. If the matrix you supply is not a 364 | * rotation matrix this will fail. 365 | * 366 | * @param matrix A column major rotation matrix 367 | */ 368 | public void setRowMajor(float[] matrix) { 369 | 370 | this.matrix.setMatrix(matrix); 371 | this.matrix.setColumnMajor(false); 372 | 373 | generateQuaternionFromMatrix(); 374 | } 375 | 376 | /** 377 | * Set this quaternion from axis angle values. All rotations are in degrees. 378 | * 379 | * @param azimuth The rotation around the z axis 380 | * @param pitch The rotation around the y axis 381 | * @param roll The rotation around the x axis 382 | */ 383 | public void setEulerAngle(float azimuth, float pitch, float roll) { 384 | 385 | double heading = Math.toRadians(roll); 386 | double attitude = Math.toRadians(pitch); 387 | double bank = Math.toRadians(azimuth); 388 | 389 | double c1 = Math.cos(heading / 2); 390 | double s1 = Math.sin(heading / 2); 391 | double c2 = Math.cos(attitude / 2); 392 | double s2 = Math.sin(attitude / 2); 393 | double c3 = Math.cos(bank / 2); 394 | double s3 = Math.sin(bank / 2); 395 | double c1c2 = c1 * c2; 396 | double s1s2 = s1 * s2; 397 | setW((float) (c1c2 * c3 - s1s2 * s3)); 398 | setX((float) (c1c2 * s3 + s1s2 * c3)); 399 | setY((float) (s1 * c2 * c3 + c1 * s2 * s3)); 400 | setZ((float) (c1 * s2 * c3 - s1 * c2 * s3)); 401 | 402 | dirty = true; 403 | } 404 | 405 | /** 406 | * Rotation is in degrees. Set this quaternion from the supplied axis angle. 407 | * 408 | * @param vec The vector of rotation 409 | * @param rot The angle of rotation around that vector in degrees. 410 | */ 411 | public void setAxisAngle(Vector3f vec, float rot) { 412 | double s = Math.sin(Math.toRadians(rot / 2)); 413 | setX(vec.getX() * (float) s); 414 | setY(vec.getY() * (float) s); 415 | setZ(vec.getZ() * (float) s); 416 | setW((float) Math.cos(Math.toRadians(rot / 2))); 417 | 418 | dirty = true; 419 | } 420 | 421 | public void setAxisAngleRad(Vector3f vec, double rot) { 422 | double s = rot / 2; 423 | setX(vec.getX() * (float) s); 424 | setY(vec.getY() * (float) s); 425 | setZ(vec.getZ() * (float) s); 426 | setW((float) rot / 2); 427 | 428 | dirty = true; 429 | } 430 | 431 | /** 432 | * @return Returns this Quaternion in the Rotation Matrix representation 433 | */ 434 | public MatrixF4x4 getMatrix4x4() { 435 | //toMatrixColMajor(); 436 | if (dirty) { 437 | convertQuatToMatrix(); 438 | dirty = false; 439 | } 440 | return this.matrix; 441 | } 442 | 443 | public void copyFromVec3(Vector3f vec, float w) { 444 | copyFromV3f(vec, w); 445 | } 446 | 447 | /** 448 | * Get a linear interpolation between this quaternion and the input quaternion, storing the result in the output 449 | * quaternion. 450 | * 451 | * @param input The quaternion to be slerped with this quaternion. 452 | * @param output The quaternion to store the result in. 453 | * @param t The ratio between the two quaternions where 0 <= t <= 1.0 . Increase value of t will bring rotation 454 | * closer to the input quaternion. 455 | */ 456 | public void slerp(Quaternion input, Quaternion output, float t) { 457 | // Calculate angle between them. 458 | //double cosHalftheta = this.dotProduct(input); 459 | Quaternion bufferQuat; 460 | float cosHalftheta = this.dotProduct(input); 461 | 462 | if (cosHalftheta < 0) { 463 | if(tmpQuaternion == null) tmpQuaternion = new Quaternion(); 464 | bufferQuat = tmpQuaternion; 465 | cosHalftheta = -cosHalftheta; 466 | bufferQuat.points[0] = (-input.points[0]); 467 | bufferQuat.points[1] = (-input.points[1]); 468 | bufferQuat.points[2] = (-input.points[2]); 469 | bufferQuat.points[3] = (-input.points[3]); 470 | } else { 471 | bufferQuat = input; 472 | } 473 | /** 474 | * if(dot < 0.95f){ 475 | * double angle = Math.acos(dot); 476 | * double ratioA = Math.sin((1 - t) * angle); 477 | * double ratioB = Math.sin(t * angle); 478 | * double divisor = Math.sin(angle); 479 | * 480 | * //Calculate Quaternion 481 | * output.setW((float)((this.getW() * ratioA + input.getW() * ratioB)/divisor)); 482 | * output.setX((float)((this.getX() * ratioA + input.getX() * ratioB)/divisor)); 483 | * output.setY((float)((this.getY() * ratioA + input.getY() * ratioB)/divisor)); 484 | * output.setZ((float)((this.getZ() * ratioA + input.getZ() * ratioB)/divisor)); 485 | * } 486 | * else{ 487 | * lerp(input, output, t); 488 | * } 489 | */ 490 | // if qa=qb or qa=-qb then theta = 0 and we can return qa 491 | if (Math.abs(cosHalftheta) >= 1.0) { 492 | output.points[0] = (this.points[0]); 493 | output.points[1] = (this.points[1]); 494 | output.points[2] = (this.points[2]); 495 | output.points[3] = (this.points[3]); 496 | } else { 497 | double sinHalfTheta = Math.sqrt(1.0 - cosHalftheta * cosHalftheta); 498 | // if theta = 180 degrees then result is not fully defined 499 | // we could rotate around any axis normal to qa or qb 500 | //if(Math.abs(sinHalfTheta) < 0.001){ 501 | //output.setW(this.getW() * 0.5f + input.getW() * 0.5f); 502 | //output.setX(this.getX() * 0.5f + input.getX() * 0.5f); 503 | //output.setY(this.getY() * 0.5f + input.getY() * 0.5f); 504 | //output.setZ(this.getZ() * 0.5f + input.getZ() * 0.5f); 505 | // lerp(bufferQuat, output, t); 506 | //} 507 | //else{ 508 | double halfTheta = Math.acos(cosHalftheta); 509 | 510 | double ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta; 511 | double ratioB = Math.sin(t * halfTheta) / sinHalfTheta; 512 | 513 | //Calculate Quaternion 514 | output.points[3] = ((float) (points[3] * ratioA + bufferQuat.points[3] * ratioB)); 515 | output.points[0] = ((float) (this.points[0] * ratioA + bufferQuat.points[0] * ratioB)); 516 | output.points[1] = ((float) (this.points[1] * ratioA + bufferQuat.points[1] * ratioB)); 517 | output.points[2] = ((float) (this.points[2] * ratioA + bufferQuat.points[2] * ratioB)); 518 | 519 | //} 520 | } 521 | } 522 | 523 | } 524 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/representation/Vector3f.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.representation; 2 | 3 | /** 4 | * 3-dimensional vector with conventient getters and setters. Additionally this class is serializable and 5 | */ 6 | public class Vector3f { 7 | 8 | /** 9 | * A float array was chosen instead of individual variables due to performance concerns. Converting the points into 10 | * an array at run time can cause slowness so instead we use one array and extract the individual variables with get 11 | * methods. 12 | */ 13 | protected float[] points = new float[3]; 14 | 15 | /** 16 | * Initialises the vector with the given values 17 | * 18 | * @param x the x-component 19 | * @param y the y-component 20 | * @param z the z-component 21 | */ 22 | public Vector3f(float x, float y, float z) { 23 | this.points[0] = x; 24 | this.points[1] = y; 25 | this.points[2] = z; 26 | } 27 | 28 | /** 29 | * Initialises all components of this vector with the given same value. 30 | * 31 | * @param value Initialisation value for all components 32 | */ 33 | public Vector3f(float value) { 34 | this.points[0] = value; 35 | this.points[1] = value; 36 | this.points[2] = value; 37 | } 38 | 39 | /** 40 | * Instantiates a new vector3f. 41 | */ 42 | public Vector3f() { 43 | } 44 | 45 | /** 46 | * Copy constructor 47 | */ 48 | public Vector3f(Vector3f vector) { 49 | this.points[0] = vector.points[0]; 50 | this.points[1] = vector.points[1]; 51 | this.points[2] = vector.points[2]; 52 | } 53 | 54 | /** 55 | * Initialises this vector from a 4-dimensional vector. If the fourth component is not zero, a normalisation of all 56 | * components will be performed. 57 | * 58 | * @param vector The 4-dimensional vector that should be used for initialisation 59 | */ 60 | public Vector3f(Vector4f vector) { 61 | if (vector.w() != 0) { 62 | this.points[0] = vector.x() / vector.w(); 63 | this.points[1] = vector.y() / vector.w(); 64 | this.points[2] = vector.z() / vector.w(); 65 | } else { 66 | this.points[0] = vector.x(); 67 | this.points[1] = vector.y(); 68 | this.points[2] = vector.z(); 69 | } 70 | } 71 | 72 | /** 73 | * Returns this vector as float-array. 74 | * 75 | * @return the float[] 76 | */ 77 | public float[] toArray() { 78 | return this.points; 79 | } 80 | 81 | /** 82 | * Adds a vector to this vector 83 | * 84 | * @param summand the vector that should be added component-wise 85 | */ 86 | public void add(Vector3f summand) { 87 | this.points[0] += summand.points[0]; 88 | this.points[1] += summand.points[1]; 89 | this.points[2] += summand.points[2]; 90 | } 91 | 92 | /** 93 | * Adds the value to all components of this vector 94 | * 95 | * @param summand The value that should be added to all components 96 | */ 97 | public void add(float summand) { 98 | this.points[0] += summand; 99 | this.points[1] += summand; 100 | this.points[2] += summand; 101 | } 102 | 103 | /** 104 | * 105 | * @param subtrahend 106 | */ 107 | public void subtract(Vector3f subtrahend) { 108 | this.points[0] -= subtrahend.points[0]; 109 | this.points[1] -= subtrahend.points[1]; 110 | this.points[2] -= subtrahend.points[2]; 111 | } 112 | 113 | /** 114 | * Multiply by scalar. 115 | * 116 | * @param scalar the scalar 117 | */ 118 | public void multiplyByScalar(float scalar) { 119 | this.points[0] *= scalar; 120 | this.points[1] *= scalar; 121 | this.points[2] *= scalar; 122 | } 123 | 124 | /** 125 | * Normalize. 126 | */ 127 | public void normalize() { 128 | 129 | double a = Math.sqrt(points[0] * points[0] + points[1] * points[1] + points[2] * points[2]); 130 | this.points[0] = (float) (this.points[0] / a); 131 | this.points[1] = (float) (this.points[1] / a); 132 | this.points[2] = (float) (this.points[2] / a); 133 | 134 | } 135 | 136 | /** 137 | * Gets the x. 138 | * 139 | * @return the x 140 | */ 141 | public float getX() { 142 | return points[0]; 143 | } 144 | 145 | /** 146 | * Gets the y. 147 | * 148 | * @return the y 149 | */ 150 | public float getY() { 151 | return points[1]; 152 | } 153 | 154 | /** 155 | * Gets the z. 156 | * 157 | * @return the z 158 | */ 159 | public float getZ() { 160 | return points[2]; 161 | } 162 | 163 | /** 164 | * Sets the x. 165 | * 166 | * @param x the new x 167 | */ 168 | public void setX(float x) { 169 | this.points[0] = x; 170 | } 171 | 172 | /** 173 | * Sets the y. 174 | * 175 | * @param y the new y 176 | */ 177 | public void setY(float y) { 178 | this.points[1] = y; 179 | } 180 | 181 | /** 182 | * Sets the z. 183 | * 184 | * @param z the new z 185 | */ 186 | public void setZ(float z) { 187 | this.points[2] = z; 188 | } 189 | 190 | /** 191 | * Functions for convenience 192 | */ 193 | 194 | public float x() { 195 | return this.points[0]; 196 | } 197 | 198 | public float y() { 199 | return this.points[1]; 200 | } 201 | 202 | public float z() { 203 | return this.points[2]; 204 | } 205 | 206 | public void x(float x) { 207 | this.points[0] = x; 208 | } 209 | 210 | public void y(float y) { 211 | this.points[1] = y; 212 | } 213 | 214 | public void z(float z) { 215 | this.points[2] = z; 216 | } 217 | 218 | public void setXYZ(float x, float y, float z) { 219 | this.points[0] = x; 220 | this.points[1] = y; 221 | this.points[2] = z; 222 | } 223 | 224 | /** 225 | * Return the dot product of this vector with the input vector 226 | * 227 | * @param inputVec The vector you want to do the dot product with against this vector. 228 | * @return Float value representing the scalar of the dot product operation 229 | */ 230 | public float dotProduct(Vector3f inputVec) { 231 | return points[0] * inputVec.points[0] + points[1] * inputVec.points[1] + points[2] * inputVec.points[2]; 232 | 233 | } 234 | 235 | /** 236 | * Get the cross product of this vector and another vector. The result will be stored in the output vector. 237 | * 238 | * @param inputVec The vector you want to get the dot product of against this vector. 239 | * @param outputVec The vector to store the result in. 240 | */ 241 | public void crossProduct(Vector3f inputVec, Vector3f outputVec) { 242 | outputVec.setX(points[1] * inputVec.points[2] - points[2] * inputVec.points[1]); 243 | outputVec.setY(points[2] * inputVec.points[0] - points[0] * inputVec.points[2]); 244 | outputVec.setZ(points[0] * inputVec.points[1] - points[1] * inputVec.points[0]); 245 | } 246 | 247 | /** 248 | * If you need to get the length of a vector then use this function. 249 | * 250 | * @return The length of the vector 251 | */ 252 | public float getLength() { 253 | return (float) Math.sqrt(points[0] * points[0] + points[1] * points[1] + points[2] * points[2]); 254 | } 255 | 256 | @Override 257 | public String toString() { 258 | return "X:" + points[0] + " Y:" + points[1] + " Z:" + points[2]; 259 | } 260 | 261 | /** 262 | * Clone the input vector so that this vector has the same values. 263 | * 264 | * @param source The vector you want to clone. 265 | */ 266 | public void set(Vector3f source) { 267 | set(source.points); 268 | } 269 | 270 | /** 271 | * Clone the input vector so that this vector has the same values. 272 | * 273 | * @param source The vector you want to clone. 274 | */ 275 | public void set(float[] source) { 276 | System.arraycopy(source, 0, points, 0, 3); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /app/src/main/java/dac/zjms/com/compass/representation/Vector4f.java: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass.representation; 2 | 3 | /** 4 | * Representation of a four-dimensional float-vector 5 | */ 6 | public class Vector4f { 7 | 8 | /** The points. */ 9 | protected float points[] = { 0, 0, 0, 0 }; 10 | 11 | /** 12 | * Instantiates a new vector4f. 13 | * 14 | * @param x the x 15 | * @param y the y 16 | * @param z the z 17 | * @param w the w 18 | */ 19 | public Vector4f(float x, float y, float z, float w) { 20 | this.points[0] = x; 21 | this.points[1] = y; 22 | this.points[2] = z; 23 | this.points[3] = w; 24 | } 25 | 26 | /** 27 | * Instantiates a new vector4f. 28 | */ 29 | public Vector4f() { 30 | this.points[0] = 0; 31 | this.points[1] = 0; 32 | this.points[2] = 0; 33 | this.points[3] = 0; 34 | } 35 | 36 | public Vector4f(Vector3f vector3f, float w) { 37 | this.points[0] = vector3f.x(); 38 | this.points[1] = vector3f.y(); 39 | this.points[2] = vector3f.z(); 40 | this.points[3] = w; 41 | } 42 | 43 | /** 44 | * To array. 45 | * 46 | * @return the float[] 47 | */ 48 | public float[] array() { 49 | return points; 50 | } 51 | 52 | public void copyVec4(Vector4f vec) { 53 | this.points[0] = vec.points[0]; 54 | this.points[1] = vec.points[1]; 55 | this.points[2] = vec.points[2]; 56 | this.points[3] = vec.points[3]; 57 | } 58 | 59 | /** 60 | * Adds the. 61 | * 62 | * @param vector the vector 63 | */ 64 | public void add(Vector4f vector) { 65 | this.points[0] += vector.points[0]; 66 | this.points[1] += vector.points[1]; 67 | this.points[2] += vector.points[2]; 68 | this.points[3] += vector.points[3]; 69 | } 70 | 71 | public void add(Vector3f vector, float w) { 72 | this.points[0] += vector.x(); 73 | this.points[1] += vector.y(); 74 | this.points[2] += vector.z(); 75 | this.points[3] += w; 76 | } 77 | 78 | public void subtract(Vector4f vector) { 79 | this.points[0] -= vector.points[0]; 80 | this.points[1] -= vector.points[1]; 81 | this.points[2] -= vector.points[2]; 82 | this.points[3] -= vector.points[3]; 83 | } 84 | 85 | public void subtract(Vector4f vector, Vector4f output) { 86 | output.setXYZW(this.points[0] - vector.points[0], this.points[1] - vector.points[1], this.points[2] 87 | - vector.points[2], this.points[3] - vector.points[3]); 88 | } 89 | 90 | public void subdivide(Vector4f vector) { 91 | this.points[0] /= vector.points[0]; 92 | this.points[1] /= vector.points[1]; 93 | this.points[2] /= vector.points[2]; 94 | this.points[3] /= vector.points[3]; 95 | } 96 | 97 | /** 98 | * Multiply by scalar. 99 | * 100 | * @param scalar the scalar 101 | */ 102 | public void multiplyByScalar(float scalar) { 103 | this.points[0] *= scalar; 104 | this.points[1] *= scalar; 105 | this.points[2] *= scalar; 106 | this.points[3] *= scalar; 107 | } 108 | 109 | public float dotProduct(Vector4f input) { 110 | return this.points[0] * input.points[0] + this.points[1] * input.points[1] + this.points[2] * input.points[2] 111 | + this.points[3] * input.points[3]; 112 | } 113 | 114 | /** 115 | * Linear interpolation between two vectors storing the result in the output variable. 116 | * 117 | * @param input 118 | * @param output 119 | * @param t 120 | */ 121 | public void lerp(Vector4f input, Vector4f output, float t) { 122 | output.points[0] = (points[0] * (1.0f * t) + input.points[0] * t); 123 | output.points[1] = (points[1] * (1.0f * t) + input.points[1] * t); 124 | output.points[2] = (points[2] * (1.0f * t) + input.points[2] * t); 125 | output.points[3] = (points[3] * (1.0f * t) + input.points[3] * t); 126 | 127 | } 128 | 129 | /** 130 | * Normalize. 131 | */ 132 | public void normalize() { 133 | if (points[3] == 0) 134 | return; 135 | 136 | points[0] /= points[3]; 137 | points[1] /= points[3]; 138 | points[2] /= points[3]; 139 | 140 | double a = Math.sqrt(this.points[0] * this.points[0] + this.points[1] * this.points[1] + this.points[2] 141 | * this.points[2]); 142 | points[0] = (float) (this.points[0] / a); 143 | points[1] = (float) (this.points[1] / a); 144 | points[2] = (float) (this.points[2] / a); 145 | } 146 | 147 | /** 148 | * Gets the x. 149 | * 150 | * @return the x 151 | */ 152 | public float getX() { 153 | return this.points[0]; 154 | } 155 | 156 | /** 157 | * Gets the y. 158 | * 159 | * @return the y 160 | */ 161 | public float getY() { 162 | return this.points[1]; 163 | } 164 | 165 | /** 166 | * Gets the z. 167 | * 168 | * @return the z 169 | */ 170 | public float getZ() { 171 | return this.points[2]; 172 | } 173 | 174 | /** 175 | * Gets the w. 176 | * 177 | * @return the w 178 | */ 179 | public float getW() { 180 | return this.points[3]; 181 | } 182 | 183 | /** 184 | * Sets the x. 185 | * 186 | * @param x the new x 187 | */ 188 | public void setX(float x) { 189 | this.points[0] = x; 190 | } 191 | 192 | /** 193 | * Sets the y. 194 | * 195 | * @param y the new y 196 | */ 197 | public void setY(float y) { 198 | this.points[1] = y; 199 | } 200 | 201 | /** 202 | * Sets the z. 203 | * 204 | * @param z the new z 205 | */ 206 | public void setZ(float z) { 207 | this.points[2] = z; 208 | } 209 | 210 | /** 211 | * Sets the w. 212 | * 213 | * @param w the new w 214 | */ 215 | public void setW(float w) { 216 | this.points[3] = w; 217 | } 218 | 219 | public float x() { 220 | return this.points[0]; 221 | } 222 | 223 | public float y() { 224 | return this.points[1]; 225 | } 226 | 227 | public float z() { 228 | return this.points[2]; 229 | } 230 | 231 | public float w() { 232 | return this.points[3]; 233 | } 234 | 235 | public void x(float x) { 236 | this.points[0] = x; 237 | } 238 | 239 | public void y(float y) { 240 | this.points[1] = y; 241 | } 242 | 243 | public void z(float z) { 244 | this.points[2] = z; 245 | } 246 | 247 | public void w(float w) { 248 | this.points[3] = w; 249 | } 250 | 251 | public void setXYZW(float x, float y, float z, float w) { 252 | this.points[0] = x; 253 | this.points[1] = y; 254 | this.points[2] = z; 255 | this.points[3] = w; 256 | } 257 | 258 | /** 259 | * Compare this vector4f to the supplied one 260 | * 261 | * @param rhs True if they match, false other wise. 262 | * @return 263 | */ 264 | public boolean compareTo(Vector4f rhs) { 265 | boolean ret = false; 266 | if (this.points[0] == rhs.points[0] && this.points[1] == rhs.points[1] && this.points[2] == rhs.points[2] 267 | && this.points[3] == rhs.points[3]) 268 | ret = true; 269 | return ret; 270 | } 271 | 272 | /** 273 | * Copies the data from the supplied vec3 into this vec4 plus the supplied w. 274 | * 275 | * @param input The x y z values to copy in. 276 | * @param w The extra w element to copy in 277 | */ 278 | public void copyFromV3f(Vector3f input, float w) { 279 | points[0] = (input.x()); 280 | points[1] = (input.y()); 281 | points[2] = (input.z()); 282 | points[3] = (w); 283 | } 284 | 285 | @Override 286 | public String toString() { 287 | return "X:" + points[0] + " Y:" + points[1] + " Z:" + points[2] + " W:" + points[3]; 288 | } 289 | 290 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 92 | 93 | 98 | 99 | 106 | 107 | 114 | 115 | 116 | 129 | 130 | 134 | 135 | 136 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxxhdpi/compass.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/imagebutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxxhdpi/imagebutton.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/app/src/main/res/mipmap-xxxhdpi/level.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs_level_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Compass 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/dac/zjms/com/compass/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dac.zjms.com.compass 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.51' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.4' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangHwYD/Compass/7481516c3bc60a1ede1676538391e558760c9adc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 19 11:29:54 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------