Migrated project to Android Studio

This commit is contained in:
Alexander Pacha
2016-02-28 18:30:08 +01:00
parent 193cb0c15d
commit 0a761b94f6
56 changed files with 341 additions and 192 deletions

23
app/build.gradle Normal file
View File

@@ -0,0 +1,23 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "org.hitlabnz.sensor_fusion_demo"
minSdkVersion 11
targetSdkVersion 23
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile 'com.android.support:support-v4:23.2.0'
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.hitlabnz.sensor_fusion_demo"
android:versionCode="3"
android:versionName="1.2" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="19" />
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="org.hitlabnz.sensor_fusion_demo.SensorSelectionActivity"
android:label="@string/title_activity_sensor_selection"
android:screenOrientation="nosensor" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="org.hitlabnz.sensor_fusion_demo.AboutActivity"
android:label="@string/title_activity_about" >
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,57 @@
<html>
<body link="#ff8800" vlink="#ff8800" alink="#ff8800">
<p>
Diese App wurde von <a href="http://my-it.at">Alexander Pacha</a> am <a
href="http://www.hitlabnz.org">Human Interface Technology
Laboratory New Zealand</a> entwickelt und demonstriert die
Leistungsfähigkeit von verschiedenen Sensoren und Sensorfusionen.
Messungen von dem Gyroskop, Akzelerometer und Kompass werden in
verschiedenen Weisen kombiniert und das Ergebnis wird als Würfel
visualisiert, der durch rotieren des Gerätes gedreht werden kann.
</p>
<p>
Die große Neuheit in dieser Applikation ist die Fusion von zwei
virtuellen Sensoren: <b>Improved Orientation Sensor 1</b> und <b>Improved
Orientation Sensor 2</b> nutzen den Android Rotation Vector mit dem
kalibrierten Gyroskopsensor und erreichen eine nie zuvor dagewesenen Präzision
und Reaktionsfähigkeit.
</p>
<p>Neben diesen beiden Sensorfusionen gibt es noch weitere Sensoren
zum Vergleich:</p>
<ul>
<li>Improved Orientation Sensor 1 (Sensorfusion des Android
Rotation Vector und des kalibrierten Gyroskops - weniger stabil,
dafür genauer)</li>
<li>Improved Orientation Sensor 2 (Sensorfusion des Android
Rotation Vector und des kalibrierten Gyroskops - stabiler, dafür
ungenauer)</li>
<li>Android Rotation Vector (Kalmanfilterfusion von Akzelerometer
+ Gyroskop + Kompass) - die bisher beste verfügbare Fusion!</li>
<li>Kalibriertes Gyroskop (Weiteres Ergebnis der
Kalmanfilterfusion von Akzelerometer + Gyroskop + Kompass). Liefert
nur relative Rotation, kann daher von den anderen Sensoren abweichen.</li>
<li>Gravitation + Kompass</li>
<li>Akzelerometer + Kompass</li>
</ul>
<p>
Die Anwendung wurde entwickelt um die im Rahmer der <a
href="http://my-it.at/index.php/publikationen">Masterarbeit</a> "Sensor
fusion for robust outdoor Augmented Reality tracking on mobile devices"
<a href="http://my-it.at/media/MasterThesis-Pacha.pdf">(download)</a>
entwickelte Sensorfusion zu demonstrieren. Die App wurde am <a
href="http://www.hitlabnz.org">Human Interface Technology
Laboratory New Zealand</a> entwickelt.
</p>
<p>Lange auf den Würfel gedrückt halten, um kurzfristig in den Raummodus zu wechseln.</p>
<p>
Der Quellcode ist öffentlich verfügbar auf <a
href="https://bitbucket.org/apacha/sensor-fusion-demo/">Bitbucket</a>
unter der MIT Lizenz.
</p>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<html>
<body link="#ff8800" vlink="#ff8800" alink="#ff8800">
<p>This application was developed by <a href="http://my-it.at">Alexander Pacha</a> at the <a href="http://www.hitlabnz.org">Human Interface
Technology Laboratory New Zealand</a> to demonstrate the
capabilities of various sensors and sensor fusion approaches. Data
from the Gyroscope, Accelerometer and compass are combined in
different ways and the result is shown as a cube that can be rotated
by rotating the device.</p>
<p>
The major novelty in this application is the fusion of virtual
sensors: <b>Improved Orientation Sensor 1</b> and <b>Improved
Orientation Sensor 2</b> fuse the Android Rotation Vector with the
virtual Gyroscope sensor to achieve a pose estimation with a
previously unknown stability and precision.
</p>
<p>Apart from these two sensors, the following sensors are
available for comparison:</p>
<ul>
<li>Improved Orientation Sensor 1 (Sensor fusion of Android
Rotation Vector and Calibrated Gyroscope - less stable but more
accurate)</li>
<li>Improved Orientation Sensor 2 (Sensor fusion of Android
Rotation Vector and Calibrated Gyroscope - more stable but less
accurate)</li>
<li>Android Rotation Vector (Kalman filter fusion of
Accelerometer + Gyroscope + Compass)</li>
<li>Calibrated Gyroscope (Separate result of Kalman filter fusion
of Accelerometer + Gyroscope + Compass)</li>
<li>Gravity + Compass</li>
<li>Accelerometer + Compass</li>
</ul>
<p>
This application was developed for demonstrating the sensor fusion
approach developed for my <a
href="http://my-it.at/index.php/publikationen">Master thesis</a> "Sensor fusion for robust outdoor Augmented Reality tracking on mobile
devices"
<a href="http://my-it.at/media/MasterThesis-Pacha.pdf">(download)</a> at the <a href="http://www.hitlabnz.org">Human Interface
Technology Laboratory New Zealand</a>
</p>
<p>Long-click on a cube to temporarily change into the space-mode for this fusion.</p>
<p>
The source-code is publicly available at <a
href="https://bitbucket.org/apacha/sensor-fusion-demo/">Bitbucket</a>
and licensed under the MIT license.
</p>
</body>
</html>

View File

@@ -0,0 +1,43 @@
package org.hitlabnz.sensor_fusion_demo;
import java.util.Locale;
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
/**
* Activity, that displays a single WebView with the text shown under the section About in the settings
*
* @author Alexander Pacha
*
*/
public class AboutActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
// Get the locale substring to access the localised assets
String localPrefix = Locale.getDefault().getLanguage().substring(0, 2).toLowerCase(Locale.US);
// Load the website as the only action for this activity
WebView webView = (WebView) findViewById(R.id.webViewAbout);
webView.loadUrl("file:///android_asset/about/" + localPrefix + "/index.html");
// Enable the logo in the top left corner to bring the user back to another activity.
getActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,69 @@
package org.hitlabnz.sensor_fusion_demo;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
/**
* A simple colour-cube that is used for drawing the current rotation of the device
*
*/
public class Cube {
/**
* Buffer for the vertices
*/
private FloatBuffer mVertexBuffer;
/**
* Buffer for the colours
*/
private FloatBuffer mColorBuffer;
/**
* Buffer for indices
*/
private ByteBuffer mIndexBuffer;
/**
* Initialises a new instance of the cube
*/
public Cube() {
final float vertices[] = { -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, };
final float colors[] = { 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, };
final byte indices[] = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6,
5, 3, 0, 1, 3, 1, 2 };
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);
}
/**
* Draws this cube of the given GL-Surface
*
* @param gl The GL-Surface this cube should be drawn upon.
*/
public void draw(GL10 gl) {
gl.glEnable(GL10.GL_CULL_FACE);
gl.glFrontFace(GL10.GL_CW);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
}
}

View File

@@ -0,0 +1,167 @@
package org.hitlabnz.sensor_fusion_demo;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.OrientationProvider;
import org.hitlabnz.sensor_fusion_demo.representation.Quaternion;
import android.opengl.GLSurfaceView;
/**
* Class that implements the rendering of a cube with the current rotation of the device that is provided by a
* OrientationProvider
*
* @author Alexander Pacha
*
*/
public class CubeRenderer implements GLSurfaceView.Renderer {
/**
* The colour-cube that is drawn repeatedly
*/
private Cube mCube;
/**
* The current provider of the device orientation.
*/
private OrientationProvider orientationProvider = null;
/**
* Initialises a new CubeRenderer
*/
public CubeRenderer() {
mCube = new Cube();
}
/**
* Sets the orientationProvider of this renderer. Use this method to change which sensor fusion should be currently
* used for rendering the cube. Simply exchange it with another orientationProvider and the cube will be rendered
* with another approach.
*
* @param orientationProvider The new orientation provider that delivers the current orientation of the device
*/
public void setOrientationProvider(OrientationProvider orientationProvider) {
this.orientationProvider = orientationProvider;
}
/**
* Perform the actual rendering of the cube for each frame
*
* @param gl The surface on which the cube should be rendered
*/
public void onDrawFrame(GL10 gl) {
// clear screen
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// set-up modelview matrix
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
if (showCubeInsideOut) {
float dist = 3;
gl.glTranslatef(0, 0, -dist);
if (orientationProvider != null) {
// All Orientation providers deliver Quaternion as well as rotation matrix.
// Use your favourite representation:
// Get the rotation from the current orientationProvider as rotation matrix
//gl.glMultMatrixf(orientationProvider.getRotationMatrix().getMatrix(), 0);
// Get the rotation from the current orientationProvider as quaternion
Quaternion q = orientationProvider.getQuaternion();
gl.glRotatef((float) (2.0f * Math.acos(q.getW()) * 180.0f / Math.PI), q.getX(), q.getY(), q.getZ());
}
// draw our object
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
mCube.draw(gl);
} else {
if (orientationProvider != null) {
// All Orientation providers deliver Quaternion as well as rotation matrix.
// Use your favourite representation:
// Get the rotation from the current orientationProvider as rotation matrix
//gl.glMultMatrixf(orientationProvider.getRotationMatrix().getMatrix(), 0);
// Get the rotation from the current orientationProvider as quaternion
Quaternion q = orientationProvider.getQuaternion();
gl.glRotatef((float) (2.0f * Math.acos(q.getW()) * 180.0f / Math.PI), q.getX(), q.getY(), q.getZ());
}
float dist = 3;
drawTranslatedCube(gl, 0, 0, -dist);
drawTranslatedCube(gl, 0, 0, dist);
drawTranslatedCube(gl, 0, -dist, 0);
drawTranslatedCube(gl, 0, dist, 0);
drawTranslatedCube(gl, -dist, 0, 0);
drawTranslatedCube(gl, dist, 0, 0);
}
// draw our object
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
mCube.draw(gl);
}
/**
* Draws a translated cube
*
* @param gl the surface
* @param translateX x-translation
* @param translateY y-translation
* @param translateZ z-translation
*/
private void drawTranslatedCube(GL10 gl, float translateX, float translateY, float translateZ) {
gl.glPushMatrix();
gl.glTranslatef(translateX, translateY, translateZ);
// draw our object
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
mCube.draw(gl);
gl.glPopMatrix();
}
/**
* Update view-port with the new surface
*
* @param gl the surface
* @param width new width
* @param height new height
*/
public void onSurfaceChanged(GL10 gl, int width, int height) {
// set view-port
gl.glViewport(0, 0, width, height);
// set projection matrix
float ratio = (float) width / height;
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// dither is enabled by default, we don't need it
gl.glDisable(GL10.GL_DITHER);
// clear screen in black
gl.glClearColor(0, 0, 0, 1);
}
/**
* Flag indicating whether you want to view inside out, or outside in
*/
private boolean showCubeInsideOut = true;
/**
* Toggles whether the cube will be shown inside-out or outside in.
*/
public void toggleShowCubeInsideOut() {
this.showCubeInsideOut = !showCubeInsideOut;
}
}

View File

@@ -0,0 +1,27 @@
package org.hitlabnz.sensor_fusion_demo;
import android.hardware.Sensor;
import android.hardware.SensorManager;
/**
* Class that tests availability of hardware sensors.
*
* @author Alex
*
*/
public class HardwareChecker implements SensorChecker {
boolean gyroscopeIsAvailable = false;
public HardwareChecker (SensorManager sensorManager) {
if(sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE).size() > 0) {
gyroscopeIsAvailable = true;
}
}
@Override
public boolean IsGyroscopeAvailable() {
return gyroscopeIsAvailable;
}
}

View File

@@ -0,0 +1,110 @@
package org.hitlabnz.sensor_fusion_demo;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.AccelerometerCompassProvider;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.CalibratedGyroscopeProvider;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.GravityCompassProvider;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.ImprovedOrientationSensor1Provider;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.ImprovedOrientationSensor2Provider;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.OrientationProvider;
import org.hitlabnz.sensor_fusion_demo.orientationProvider.RotationVectorProvider;
import android.hardware.SensorManager;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnLongClickListener;
/**
* A fragment that contains the same visualisation for different orientation providers
*/
public class OrientationVisualisationFragment extends Fragment {
/**
* The surface that will be drawn upon
*/
private GLSurfaceView mGLSurfaceView;
/**
* The class that renders the cube
*/
private CubeRenderer mRenderer;
/**
* The current orientation provider that delivers device orientation.
*/
private OrientationProvider currentOrientationProvider;
/**
* The fragment argument representing the section number for this
* fragment.
*/
public static final String ARG_SECTION_NUMBER = "section_number";
@Override
public void onResume() {
// Ideally a game should implement onResume() and onPause()
// to take appropriate action when the activity looses focus
super.onResume();
currentOrientationProvider.start();
mGLSurfaceView.onResume();
}
@Override
public void onPause() {
// Ideally a game should implement onResume() and onPause()
// to take appropriate action when the activity looses focus
super.onPause();
currentOrientationProvider.stop();
mGLSurfaceView.onPause();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Initialise the orientationProvider
switch (getArguments().getInt(ARG_SECTION_NUMBER)) {
case 1:
currentOrientationProvider = new ImprovedOrientationSensor1Provider((SensorManager) getActivity()
.getSystemService(SensorSelectionActivity.SENSOR_SERVICE));
break;
case 2:
currentOrientationProvider = new ImprovedOrientationSensor2Provider((SensorManager) getActivity()
.getSystemService(SensorSelectionActivity.SENSOR_SERVICE));
break;
case 3:
currentOrientationProvider = new RotationVectorProvider((SensorManager) getActivity().getSystemService(
SensorSelectionActivity.SENSOR_SERVICE));
break;
case 4:
currentOrientationProvider = new CalibratedGyroscopeProvider((SensorManager) getActivity()
.getSystemService(SensorSelectionActivity.SENSOR_SERVICE));
break;
case 5:
currentOrientationProvider = new GravityCompassProvider((SensorManager) getActivity().getSystemService(
SensorSelectionActivity.SENSOR_SERVICE));
break;
case 6:
currentOrientationProvider = new AccelerometerCompassProvider((SensorManager) getActivity()
.getSystemService(SensorSelectionActivity.SENSOR_SERVICE));
break;
default:
break;
}
// Create our Preview view and set it as the content of our Activity
mRenderer = new CubeRenderer();
mRenderer.setOrientationProvider(currentOrientationProvider);
mGLSurfaceView = new GLSurfaceView(getActivity());
mGLSurfaceView.setRenderer(mRenderer);
mGLSurfaceView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mRenderer.toggleShowCubeInsideOut();
return true;
}
});
return mGLSurfaceView;
}
}

View File

@@ -0,0 +1,11 @@
package org.hitlabnz.sensor_fusion_demo;
public interface SensorChecker {
/**
* Checks if the device that is currently running the application has a hardware gyroscope built into it.
*
* @return True, if a gyroscope is available. False otherwise.
*/
public boolean IsGyroscopeAvailable();
}

View File

@@ -0,0 +1,148 @@
package org.hitlabnz.sensor_fusion_demo;
import java.util.Locale;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.Menu;
import android.view.MenuItem;
/**
* The main activity where the user can select which sensor-fusion he wants to try out
*
* @author Alexander Pacha
*
*/
public class SensorSelectionActivity extends FragmentActivity {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a {@link android.support.v4.app.FragmentPagerAdapter} derivative,
* which
* will keep every loaded fragment in memory. If this becomes too memory
* intensive, it may be best to switch to a {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensor_selection);
// Create the adapter that will return a fragment for each of the three
// primary sections of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
// Check if device has a hardware gyroscope
SensorChecker checker = new HardwareChecker((SensorManager) getSystemService(SENSOR_SERVICE));
if(!checker.IsGyroscopeAvailable()) {
// If a gyroscope is unavailable, display a warning.
displayHardwareMissingWarning();
}
}
private void displayHardwareMissingWarning() {
AlertDialog ad = new AlertDialog.Builder(this).create();
ad.setCancelable(false); // This blocks the 'BACK' button
ad.setTitle(getResources().getString(R.string.gyroscope_missing));
ad.setMessage(getResources().getString(R.string.gyroscope_missing_message));
ad.setButton(DialogInterface.BUTTON_NEUTRAL, getResources().getString(R.string.OK), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
ad.show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.sensor_selection, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_about:
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
return true;
}
return false;
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
/**
* Initialises a new sectionPagerAdapter
*
* @param fm the fragment Manager
*/
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a DummySectionFragment (defined as a static inner class
// below) with the page number as its lone argument.
Fragment fragment = new OrientationVisualisationFragment();
Bundle args = new Bundle();
args.putInt(OrientationVisualisationFragment.ARG_SECTION_NUMBER, position + 1);
fragment.setArguments(args);
return fragment;
}
@Override
public int getCount() {
// Show 6 total pages.
return 6;
}
@Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
case 3:
return getString(R.string.title_section4).toUpperCase(l);
case 4:
return getString(R.string.title_section5).toUpperCase(l);
case 5:
return getString(R.string.title_section6).toUpperCase(l);
}
return null;
}
}
}

View File

@@ -0,0 +1,60 @@
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
/**
* The orientation provider that delivers the current orientation from the {@link Sensor#TYPE_ACCELEROMETER
* Accelerometer} and {@link Sensor#TYPE_MAGNETIC_FIELD Compass}.
*
* @author Alexander Pacha
*
*/
public class AccelerometerCompassProvider extends OrientationProvider {
/**
* Compass values
*/
private float[] magnitudeValues = new float[3];
/**
* Accelerometer values
*/
private float[] accelerometerValues = new float[3];
/**
* Initialises a new AccelerometerCompassProvider
*
* @param sensorManager The android sensor manager
*/
public AccelerometerCompassProvider(SensorManager sensorManager) {
super(sensorManager);
//Add the compass and the accelerometer
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD));
}
@Override
public void onSensorChanged(SensorEvent event) {
// we received a sensor event. it is a good practice to check
// that we received the proper event
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magnitudeValues = event.values.clone();
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
accelerometerValues = event.values.clone();
}
if (magnitudeValues != null && accelerometerValues != null) {
float[] i = new float[16];
// Fuse accelerometer with compass
SensorManager.getRotationMatrix(currentOrientationRotationMatrix.matrix, i, accelerometerValues,
magnitudeValues);
// Transform rotation matrix to quaternion
currentOrientationQuaternion.setRowMajor(currentOrientationRotationMatrix.matrix);
}
}
}

View File

@@ -0,0 +1,127 @@
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import org.hitlabnz.sensor_fusion_demo.representation.Quaternion;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
/**
* The orientation provider that delivers the relative orientation from the {@link Sensor#TYPE_GYROSCOPE
* Gyroscope}. This sensor does not deliver an absolute orientation (with respect to magnetic north and gravity) but
* only a relative measurement starting from the point where it started.
*
* @author Alexander Pacha
*
*/
public class CalibratedGyroscopeProvider extends OrientationProvider {
/**
* Constant specifying the factor between a Nano-second and a second
*/
private static final float NS2S = 1.0f / 1000000000.0f;
/**
* The quaternion that stores the difference that is obtained by the gyroscope.
* Basically it contains a rotational difference encoded into a quaternion.
*
* To obtain the absolute orientation one must add this into an initial position by
* multiplying it with another quaternion
*/
private final Quaternion deltaQuaternion = new Quaternion();
/**
* The time-stamp being used to record the time when the last gyroscope event occurred.
*/
private long timestamp;
/**
* This is a filter-threshold for discarding Gyroscope measurements that are below a certain level and
* potentially are only noise and not real motion. Values from the gyroscope are usually between 0 (stop) and
* 10 (rapid rotation), so 0.1 seems to be a reasonable threshold to filter noise (usually smaller than 0.1) and
* real motion (usually > 0.1). Note that there is a chance of missing real motion, if the use is turning the
* device really slowly, so this value has to find a balance between accepting noise (threshold = 0) and missing
* slow user-action (threshold > 0.5). 0.1 seems to work fine for most applications.
*
*/
private static final double EPSILON = 0.1f;
/**
* Value giving the total velocity of the gyroscope (will be high, when the device is moving fast and low when
* the device is standing still). This is usually a value between 0 and 10 for normal motion. Heavy shaking can
* increase it to about 25. Keep in mind, that these values are time-depended, so changing the sampling rate of
* the sensor will affect this value!
*/
private double gyroscopeRotationVelocity = 0;
/**
* Initialises a new CalibratedGyroscopeProvider
*
* @param sensorManager The android sensor manager
*/
public CalibratedGyroscopeProvider(SensorManager sensorManager) {
super(sensorManager);
//Add the gyroscope
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE));
}
@Override
public void onSensorChanged(SensorEvent event) {
// we received a sensor event. it is a good practice to check
// that we received the proper event
if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
// This timestep's delta rotation to be multiplied by the current rotation
// after computing it from the gyro sample data.
if (timestamp != 0) {
final float dT = (event.timestamp - timestamp) * NS2S;
// Axis of the rotation sample, not normalized yet.
float axisX = event.values[0];
float axisY = event.values[1];
float axisZ = event.values[2];
// Calculate the angular speed of the sample
gyroscopeRotationVelocity = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ);
// Normalize the rotation vector if it's big enough to get the axis
if (gyroscopeRotationVelocity > EPSILON) {
axisX /= gyroscopeRotationVelocity;
axisY /= gyroscopeRotationVelocity;
axisZ /= gyroscopeRotationVelocity;
}
// Integrate around this axis with the angular speed by the timestep
// in order to get a delta rotation from this sample over the timestep
// We will convert this axis-angle representation of the delta rotation
// into a quaternion before turning it into the rotation matrix.
double thetaOverTwo = gyroscopeRotationVelocity * dT / 2.0f;
double sinThetaOverTwo = Math.sin(thetaOverTwo);
double cosThetaOverTwo = Math.cos(thetaOverTwo);
deltaQuaternion.setX((float) (sinThetaOverTwo * axisX));
deltaQuaternion.setY((float) (sinThetaOverTwo * axisY));
deltaQuaternion.setZ((float) (sinThetaOverTwo * axisZ));
deltaQuaternion.setW(-(float) cosThetaOverTwo);
// Matrix rendering in CubeRenderer does not seem to have this problem.
synchronized (syncToken) {
// Move current gyro orientation if gyroscope should be used
deltaQuaternion.multiplyByQuat(currentOrientationQuaternion, currentOrientationQuaternion);
}
Quaternion correctedQuat = currentOrientationQuaternion.clone();
// We inverted w in the deltaQuaternion, because currentOrientationQuaternion required it.
// Before converting it back to matrix representation, we need to revert this process
correctedQuat.w(-correctedQuat.w());
synchronized (syncToken) {
// Set the rotation matrix as well to have both representations
SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix,
correctedQuat.ToArray());
}
}
timestamp = event.timestamp;
}
}
}

View File

@@ -0,0 +1,59 @@
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
/**
* The orientation provider that delivers the current orientation from the {@link Sensor#TYPE_GRAVITY
* Gravity} and {@link Sensor#TYPE_MAGNETIC_FIELD Compass}.
*
* @author Alexander Pacha
*
*/
public class GravityCompassProvider extends OrientationProvider {
/**
* Compass values
*/
private float[] magnitudeValues = new float[3];
/**
* Gravity values
*/
private float[] gravityValues = new float[3];
/**
* Initialises a new GravityCompassProvider
*
* @param sensorManager The android sensor manager
*/
public GravityCompassProvider(SensorManager sensorManager) {
super(sensorManager);
//Add the compass and the gravity sensor
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY));
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD));
}
@Override
public void onSensorChanged(SensorEvent event) {
// we received a sensor event. it is a good practice to check
// that we received the proper event
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magnitudeValues = event.values.clone();
} else if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
gravityValues = event.values.clone();
}
if (magnitudeValues != null && gravityValues != null) {
float[] i = new float[16];
// Fuse gravity-sensor (virtual sensor) with compass
SensorManager.getRotationMatrix(currentOrientationRotationMatrix.matrix, i, gravityValues, magnitudeValues);
// Transform rotation matrix to quaternion
currentOrientationQuaternion.setRowMajor(currentOrientationRotationMatrix.matrix);
}
}
}

View File

@@ -0,0 +1,273 @@
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import org.hitlabnz.sensor_fusion_demo.representation.Quaternion;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.util.Log;
/**
* The orientation provider that delivers the absolute orientation from the {@link Sensor#TYPE_GYROSCOPE
* Gyroscope} and {@link Sensor#TYPE_ROTATION_VECTOR Android Rotation Vector sensor}.
*
* It mainly relies on the gyroscope, but corrects with the Android Rotation Vector which also provides an absolute
* estimation of current orientation. The correction is a static weight.
*
* @author Alexander Pacha
*
*/
public class ImprovedOrientationSensor1Provider extends OrientationProvider {
/**
* Constant specifying the factor between a Nano-second and a second
*/
private static final float NS2S = 1.0f / 1000000000.0f;
/**
* The quaternion that stores the difference that is obtained by the gyroscope.
* Basically it contains a rotational difference encoded into a quaternion.
*
* To obtain the absolute orientation one must add this into an initial position by
* multiplying it with another quaternion
*/
private final Quaternion deltaQuaternion = new Quaternion();
/**
* The Quaternions that contain the current rotation (Angle and axis in Quaternion format) of the Gyroscope
*/
private Quaternion quaternionGyroscope = new Quaternion();
/**
* The quaternion that contains the absolute orientation as obtained by the rotationVector sensor.
*/
private Quaternion quaternionRotationVector = new Quaternion();
/**
* The time-stamp being used to record the time when the last gyroscope event occurred.
*/
private long timestamp;
/**
* This is a filter-threshold for discarding Gyroscope measurements that are below a certain level and
* potentially are only noise and not real motion. Values from the gyroscope are usually between 0 (stop) and
* 10 (rapid rotation), so 0.1 seems to be a reasonable threshold to filter noise (usually smaller than 0.1) and
* real motion (usually > 0.1). Note that there is a chance of missing real motion, if the use is turning the
* device really slowly, so this value has to find a balance between accepting noise (threshold = 0) and missing
* slow user-action (threshold > 0.5). 0.1 seems to work fine for most applications.
*
*/
private static final double EPSILON = 0.1f;
/**
* Value giving the total velocity of the gyroscope (will be high, when the device is moving fast and low when
* the device is standing still). This is usually a value between 0 and 10 for normal motion. Heavy shaking can
* increase it to about 25. Keep in mind, that these values are time-depended, so changing the sampling rate of
* the sensor will affect this value!
*/
private double gyroscopeRotationVelocity = 0;
/**
* Flag indicating, whether the orientations were initialised from the rotation vector or not. If false, the
* gyroscope can not be used (since it's only meaningful to calculate differences from an initial state). If
* true,
* the gyroscope can be used normally.
*/
private boolean positionInitialised = false;
/**
* Counter that sums the number of consecutive frames, where the rotationVector and the gyroscope were
* significantly different (and the dot-product was smaller than 0.7). This event can either happen when the
* angles of the rotation vector explode (e.g. during fast tilting) or when the device was shaken heavily and
* the gyroscope is now completely off.
*/
private int panicCounter;
/**
* This weight determines directly how much the rotation sensor will be used to correct (in
* Sensor-fusion-scenario 1 - SensorSelection.GyroscopeAndRotationVector). Must be a value between 0 and 1.
* 0 means that the system entirely relies on the gyroscope, whereas 1 means that the system relies entirely on
* the rotationVector.
*/
private static final float DIRECT_INTERPOLATION_WEIGHT = 0.005f;
/**
* The threshold that indicates an outlier of the rotation vector. If the dot-product between the two vectors
* (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1,
* if they are exactly the same) the system falls back to the gyroscope values only and just ignores the
* rotation vector.
*
* This value should be quite high (> 0.7) to filter even the slightest discrepancies that causes jumps when
* tiling the device. Possible values are between 0 and 1, where a value close to 1 means that even a very small
* difference between the two sensors will be treated as outlier, whereas a value close to zero means that the
* almost any discrepancy between the two sensors is tolerated.
*/
private static final float OUTLIER_THRESHOLD = 0.85f;
/**
* The threshold that indicates a massive discrepancy between the rotation vector and the gyroscope orientation.
* If the dot-product between the two vectors
* (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1, if
* they are exactly the same), the system will start increasing the panic counter (that probably indicates a
* gyroscope failure).
*
* This value should be lower than OUTLIER_THRESHOLD (0.5 - 0.7) to only start increasing the panic counter,
* when there is a
* huge discrepancy between the two fused sensors.
*/
private static final float OUTLIER_PANIC_THRESHOLD = 0.65f;
/**
* The threshold that indicates that a chaos state has been established rather than just a temporary peak in the
* rotation vector (caused by exploding angled during fast tilting).
*
* If the chaosCounter is bigger than this threshold, the current position will be reset to whatever the
* rotation vector indicates.
*/
private static final int PANIC_THRESHOLD = 60;
/**
* Initialises a new ImprovedOrientationSensor1Provider
*
* @param sensorManager The android sensor manager
*/
public ImprovedOrientationSensor1Provider(SensorManager sensorManager) {
super(sensorManager);
//Add the gyroscope and rotation Vector
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE));
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR));
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
// Process rotation vector (just safe it)
float[] q = new float[4];
// Calculate angle. Starting with API_18, Android will provide this value as event.values[3], but if not, we have to calculate it manually.
SensorManager.getQuaternionFromVector(q, event.values);
// Store in quaternion
quaternionRotationVector.setXYZW(q[1], q[2], q[3], -q[0]);
if (!positionInitialised) {
// Override
quaternionGyroscope.set(quaternionRotationVector);
positionInitialised = true;
}
} else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
// Process Gyroscope and perform fusion
// This timestep's delta rotation to be multiplied by the current rotation
// after computing it from the gyro sample data.
if (timestamp != 0) {
final float dT = (event.timestamp - timestamp) * NS2S;
// Axis of the rotation sample, not normalized yet.
float axisX = event.values[0];
float axisY = event.values[1];
float axisZ = event.values[2];
// Calculate the angular speed of the sample
gyroscopeRotationVelocity = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ);
// Normalize the rotation vector if it's big enough to get the axis
if (gyroscopeRotationVelocity > EPSILON) {
axisX /= gyroscopeRotationVelocity;
axisY /= gyroscopeRotationVelocity;
axisZ /= gyroscopeRotationVelocity;
}
// Integrate around this axis with the angular speed by the timestep
// in order to get a delta rotation from this sample over the timestep
// We will convert this axis-angle representation of the delta rotation
// into a quaternion before turning it into the rotation matrix.
double thetaOverTwo = gyroscopeRotationVelocity * dT / 2.0f;
double sinThetaOverTwo = Math.sin(thetaOverTwo);
double cosThetaOverTwo = Math.cos(thetaOverTwo);
deltaQuaternion.setX((float) (sinThetaOverTwo * axisX));
deltaQuaternion.setY((float) (sinThetaOverTwo * axisY));
deltaQuaternion.setZ((float) (sinThetaOverTwo * axisZ));
deltaQuaternion.setW(-(float) cosThetaOverTwo);
// Move current gyro orientation
deltaQuaternion.multiplyByQuat(quaternionGyroscope, quaternionGyroscope);
// Calculate dot-product to calculate whether the two orientation sensors have diverged
// (if the dot-product is closer to 0 than to 1), because it should be close to 1 if both are the same.
float dotProd = quaternionGyroscope.dotProduct(quaternionRotationVector);
// If they have diverged, rely on gyroscope only (this happens on some devices when the rotation vector "jumps").
if (Math.abs(dotProd) < OUTLIER_THRESHOLD) {
// Increase panic counter
if (Math.abs(dotProd) < OUTLIER_PANIC_THRESHOLD) {
panicCounter++;
}
// Directly use Gyro
setOrientationQuaternionAndMatrix(quaternionGyroscope);
} else {
// Both are nearly saying the same. Perform normal fusion.
// Interpolate with a fixed weight between the two absolute quaternions obtained from gyro and rotation vector sensors
// The weight should be quite low, so the rotation vector corrects the gyro only slowly, and the output keeps responsive.
Quaternion interpolate = new Quaternion();
quaternionGyroscope.slerp(quaternionRotationVector, interpolate, DIRECT_INTERPOLATION_WEIGHT);
// Use the interpolated value between gyro and rotationVector
setOrientationQuaternionAndMatrix(interpolate);
// Override current gyroscope-orientation
quaternionGyroscope.copyVec4(interpolate);
// Reset the panic counter because both sensors are saying the same again
panicCounter = 0;
}
if (panicCounter > PANIC_THRESHOLD) {
Log.d("Rotation Vector",
"Panic counter is bigger than threshold; this indicates a Gyroscope failure. Panic reset is imminent.");
if (gyroscopeRotationVelocity < 3) {
Log.d("Rotation Vector",
"Performing Panic-reset. Resetting orientation to rotation-vector value.");
// Manually set position to whatever rotation vector says.
setOrientationQuaternionAndMatrix(quaternionRotationVector);
// Override current gyroscope-orientation with corrected value
quaternionGyroscope.copyVec4(quaternionRotationVector);
panicCounter = 0;
} else {
Log.d("Rotation Vector",
String.format(
"Panic reset delayed due to ongoing motion (user is still shaking the device). Gyroscope Velocity: %.2f > 3",
gyroscopeRotationVelocity));
}
}
}
timestamp = event.timestamp;
}
}
/**
* Sets the output quaternion and matrix with the provided quaternion and synchronises the setting
*
* @param quaternion The Quaternion to set (the result of the sensor fusion)
*/
private void setOrientationQuaternionAndMatrix(Quaternion quaternion) {
Quaternion correctedQuat = quaternion.clone();
// We inverted w in the deltaQuaternion, because currentOrientationQuaternion required it.
// Before converting it back to matrix representation, we need to revert this process
correctedQuat.w(-correctedQuat.w());
synchronized (syncToken) {
// Use gyro only
currentOrientationQuaternion.copyVec4(quaternion);
// Set the rotation matrix as well to have both representations
SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix, correctedQuat.ToArray());
}
}
}

View File

@@ -0,0 +1,274 @@
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import org.hitlabnz.sensor_fusion_demo.representation.Quaternion;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.util.Log;
/**
* The orientation provider that delivers the absolute orientation from the {@link Sensor#TYPE_GYROSCOPE
* Gyroscope} and {@link Sensor#TYPE_ROTATION_VECTOR Android Rotation Vector sensor}.
*
* It mainly relies on the gyroscope, but corrects with the Android Rotation Vector which also provides an absolute
* estimation of current orientation. The correction is a static weight.
*
* @author Alexander Pacha
*
*/
public class ImprovedOrientationSensor2Provider extends OrientationProvider {
/**
* Constant specifying the factor between a Nano-second and a second
*/
private static final float NS2S = 1.0f / 1000000000.0f;
/**
* The quaternion that stores the difference that is obtained by the gyroscope.
* Basically it contains a rotational difference encoded into a quaternion.
*
* To obtain the absolute orientation one must add this into an initial position by
* multiplying it with another quaternion
*/
private final Quaternion deltaQuaternion = new Quaternion();
/**
* The Quaternions that contain the current rotation (Angle and axis in Quaternion format) of the Gyroscope
*/
private Quaternion quaternionGyroscope = new Quaternion();
/**
* The quaternion that contains the absolute orientation as obtained by the rotationVector sensor.
*/
private Quaternion quaternionRotationVector = new Quaternion();
/**
* The time-stamp being used to record the time when the last gyroscope event occurred.
*/
private long timestamp;
/**
* This is a filter-threshold for discarding Gyroscope measurements that are below a certain level and
* potentially are only noise and not real motion. Values from the gyroscope are usually between 0 (stop) and
* 10 (rapid rotation), so 0.1 seems to be a reasonable threshold to filter noise (usually smaller than 0.1) and
* real motion (usually > 0.1). Note that there is a chance of missing real motion, if the use is turning the
* device really slowly, so this value has to find a balance between accepting noise (threshold = 0) and missing
* slow user-action (threshold > 0.5). 0.1 seems to work fine for most applications.
*
*/
private static final double EPSILON = 0.1f;
/**
* Value giving the total velocity of the gyroscope (will be high, when the device is moving fast and low when
* the device is standing still). This is usually a value between 0 and 10 for normal motion. Heavy shaking can
* increase it to about 25. Keep in mind, that these values are time-depended, so changing the sampling rate of
* the sensor will affect this value!
*/
private double gyroscopeRotationVelocity = 0;
/**
* Flag indicating, whether the orientations were initialised from the rotation vector or not. If false, the
* gyroscope can not be used (since it's only meaningful to calculate differences from an initial state). If
* true,
* the gyroscope can be used normally.
*/
private boolean positionInitialised = false;
/**
* Counter that sums the number of consecutive frames, where the rotationVector and the gyroscope were
* significantly different (and the dot-product was smaller than 0.7). This event can either happen when the
* angles of the rotation vector explode (e.g. during fast tilting) or when the device was shaken heavily and
* the gyroscope is now completely off.
*/
private int panicCounter;
/**
* This weight determines indirectly how much the rotation sensor will be used to correct. This weight will be
* multiplied by the velocity to obtain the actual weight. (in sensor-fusion-scenario 2 -
* SensorSelection.GyroscopeAndRotationVector2).
* Must be a value between 0 and approx. 0.04 (because, if multiplied with a velocity of up to 25, should be still
* less than 1, otherwise the SLERP will not correctly interpolate). Should be close to zero.
*/
private static final float INDIRECT_INTERPOLATION_WEIGHT = 0.01f;
/**
* The threshold that indicates an outlier of the rotation vector. If the dot-product between the two vectors
* (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1,
* if they are exactly the same) the system falls back to the gyroscope values only and just ignores the
* rotation vector.
*
* This value should be quite high (> 0.7) to filter even the slightest discrepancies that causes jumps when
* tiling the device. Possible values are between 0 and 1, where a value close to 1 means that even a very small
* difference between the two sensors will be treated as outlier, whereas a value close to zero means that the
* almost any discrepancy between the two sensors is tolerated.
*/
private static final float OUTLIER_THRESHOLD = 0.85f;
/**
* The threshold that indicates a massive discrepancy between the rotation vector and the gyroscope orientation.
* If the dot-product between the two vectors
* (gyroscope orientation and rotationVector orientation) falls below this threshold (ideally it should be 1, if
* they are exactly the same), the system will start increasing the panic counter (that probably indicates a
* gyroscope failure).
*
* This value should be lower than OUTLIER_THRESHOLD (0.5 - 0.7) to only start increasing the panic counter,
* when there is a huge discrepancy between the two fused sensors.
*/
private static final float OUTLIER_PANIC_THRESHOLD = 0.75f;
/**
* The threshold that indicates that a chaos state has been established rather than just a temporary peak in the
* rotation vector (caused by exploding angled during fast tilting).
*
* If the chaosCounter is bigger than this threshold, the current position will be reset to whatever the
* rotation vector indicates.
*/
private static final int PANIC_THRESHOLD = 60;
/**
* Initialises a new ImprovedOrientationSensor2Provider
*
* @param sensorManager The android sensor manager
*/
public ImprovedOrientationSensor2Provider(SensorManager sensorManager) {
super(sensorManager);
//Add the gyroscope and rotation Vector
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE));
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR));
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
// Process rotation vector (just safe it)
float[] q = new float[4];
// Calculate angle. Starting with API_18, Android will provide this value as event.values[3], but if not, we have to calculate it manually.
SensorManager.getQuaternionFromVector(q, event.values);
// Store in quaternion
quaternionRotationVector.setXYZW(q[1], q[2], q[3], -q[0]);
if (!positionInitialised) {
// Override
quaternionGyroscope.set(quaternionRotationVector);
positionInitialised = true;
}
} else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
// Process Gyroscope and perform fusion
// This timestep's delta rotation to be multiplied by the current rotation
// after computing it from the gyro sample data.
if (timestamp != 0) {
final float dT = (event.timestamp - timestamp) * NS2S;
// Axis of the rotation sample, not normalized yet.
float axisX = event.values[0];
float axisY = event.values[1];
float axisZ = event.values[2];
// Calculate the angular speed of the sample
gyroscopeRotationVelocity = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ);
// Normalize the rotation vector if it's big enough to get the axis
if (gyroscopeRotationVelocity > EPSILON) {
axisX /= gyroscopeRotationVelocity;
axisY /= gyroscopeRotationVelocity;
axisZ /= gyroscopeRotationVelocity;
}
// Integrate around this axis with the angular speed by the timestep
// in order to get a delta rotation from this sample over the timestep
// We will convert this axis-angle representation of the delta rotation
// into a quaternion before turning it into the rotation matrix.
double thetaOverTwo = gyroscopeRotationVelocity * dT / 2.0f;
double sinThetaOverTwo = Math.sin(thetaOverTwo);
double cosThetaOverTwo = Math.cos(thetaOverTwo);
deltaQuaternion.setX((float) (sinThetaOverTwo * axisX));
deltaQuaternion.setY((float) (sinThetaOverTwo * axisY));
deltaQuaternion.setZ((float) (sinThetaOverTwo * axisZ));
deltaQuaternion.setW(-(float) cosThetaOverTwo);
// Move current gyro orientation
deltaQuaternion.multiplyByQuat(quaternionGyroscope, quaternionGyroscope);
// Calculate dot-product to calculate whether the two orientation sensors have diverged
// (if the dot-product is closer to 0 than to 1), because it should be close to 1 if both are the same.
float dotProd = quaternionGyroscope.dotProduct(quaternionRotationVector);
// If they have diverged, rely on gyroscope only (this happens on some devices when the rotation vector "jumps").
if (Math.abs(dotProd) < OUTLIER_THRESHOLD) {
// Increase panic counter
if (Math.abs(dotProd) < OUTLIER_PANIC_THRESHOLD) {
panicCounter++;
}
// Directly use Gyro
setOrientationQuaternionAndMatrix(quaternionGyroscope);
} else {
// Both are nearly saying the same. Perform normal fusion.
// Interpolate with a fixed weight between the two absolute quaternions obtained from gyro and rotation vector sensors
// The weight should be quite low, so the rotation vector corrects the gyro only slowly, and the output keeps responsive.
Quaternion interpolate = new Quaternion();
quaternionGyroscope.slerp(quaternionRotationVector, interpolate,
(float) (INDIRECT_INTERPOLATION_WEIGHT * gyroscopeRotationVelocity));
// Use the interpolated value between gyro and rotationVector
setOrientationQuaternionAndMatrix(interpolate);
// Override current gyroscope-orientation
quaternionGyroscope.copyVec4(interpolate);
// Reset the panic counter because both sensors are saying the same again
panicCounter = 0;
}
if (panicCounter > PANIC_THRESHOLD) {
Log.d("Rotation Vector",
"Panic counter is bigger than threshold; this indicates a Gyroscope failure. Panic reset is imminent.");
if (gyroscopeRotationVelocity < 3) {
Log.d("Rotation Vector",
"Performing Panic-reset. Resetting orientation to rotation-vector value.");
// Manually set position to whatever rotation vector says.
setOrientationQuaternionAndMatrix(quaternionRotationVector);
// Override current gyroscope-orientation with corrected value
quaternionGyroscope.copyVec4(quaternionRotationVector);
panicCounter = 0;
} else {
Log.d("Rotation Vector",
String.format(
"Panic reset delayed due to ongoing motion (user is still shaking the device). Gyroscope Velocity: %.2f > 3",
gyroscopeRotationVelocity));
}
}
}
timestamp = event.timestamp;
}
}
/**
* Sets the output quaternion and matrix with the provided quaternion and synchronises the setting
*
* @param quaternion The Quaternion to set (the result of the sensor fusion)
*/
private void setOrientationQuaternionAndMatrix(Quaternion quaternion) {
Quaternion correctedQuat = quaternion.clone();
// We inverted w in the deltaQuaternion, because currentOrientationQuaternion required it.
// Before converting it back to matrix representation, we need to revert this process
correctedQuat.w(-correctedQuat.w());
synchronized (syncToken) {
// Use gyro only
currentOrientationQuaternion.copyVec4(quaternion);
// Set the rotation matrix as well to have both representations
SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix, correctedQuat.ToArray());
}
}
}

View File

@@ -0,0 +1,130 @@
/**
*
*/
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import java.util.ArrayList;
import java.util.List;
import org.hitlabnz.sensor_fusion_demo.representation.EulerAngles;
import org.hitlabnz.sensor_fusion_demo.representation.Matrixf4x4;
import org.hitlabnz.sensor_fusion_demo.representation.Quaternion;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
/**
* Classes implementing this interface provide an orientation of the device
* either by directly accessing hardware, using Android sensor fusion or fusing
* sensors itself.
*
* The orientation can be provided as rotation matrix or quaternion.
*
* @author Alexander Pacha
*
*/
public abstract class OrientationProvider implements SensorEventListener {
/**
* Sync-token for syncing read/write to sensor-data from sensor manager and
* fusion algorithm
*/
protected final Object syncToken = new Object();
/**
* The list of sensors used by this provider
*/
protected List<Sensor> sensorList = new ArrayList<Sensor>();
/**
* The matrix that holds the current rotation
*/
protected final Matrixf4x4 currentOrientationRotationMatrix;
/**
* The quaternion that holds the current rotation
*/
protected final Quaternion currentOrientationQuaternion;
/**
* The sensor manager for accessing android sensors
*/
protected SensorManager sensorManager;
/**
* Initialises a new OrientationProvider
*
* @param sensorManager
* The android sensor manager
*/
public OrientationProvider(SensorManager sensorManager) {
this.sensorManager = sensorManager;
// Initialise with identity
currentOrientationRotationMatrix = new Matrixf4x4();
// Initialise with identity
currentOrientationQuaternion = new Quaternion();
}
/**
* Starts the sensor fusion (e.g. when resuming the activity)
*/
public void start() {
// enable our sensor when the activity is resumed, ask for
// 10 ms updates.
for (Sensor sensor : sensorList) {
// enable our sensors when the activity is resumed, ask for
// 20 ms updates (Sensor_delay_game)
sensorManager.registerListener(this, sensor,
SensorManager.SENSOR_DELAY_GAME);
}
}
/**
* Stops the sensor fusion (e.g. when pausing/suspending the activity)
*/
public void stop() {
// make sure to turn our sensors off when the activity is paused
for (Sensor sensor : sensorList) {
sensorManager.unregisterListener(this, sensor);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Not doing anything
}
/**
* @return Returns the current rotation of the device in the rotation matrix
* format (4x4 matrix)
*/
public Matrixf4x4 getRotationMatrix() {
synchronized (syncToken) {
return currentOrientationRotationMatrix;
}
}
/**
* @return Returns the current rotation of the device in the quaternion
* format (vector4f)
*/
public Quaternion getQuaternion() {
synchronized (syncToken) {
return currentOrientationQuaternion.clone();
}
}
/**
* @return Returns the current rotation of the device in the Euler-Angles
*/
public EulerAngles getEulerAngles() {
synchronized (syncToken) {
float[] angles = new float[3];
SensorManager.getOrientation(currentOrientationRotationMatrix.matrix, angles);
return new EulerAngles(angles[0], angles[1], angles[2]);
}
}
}

View File

@@ -0,0 +1,45 @@
package org.hitlabnz.sensor_fusion_demo.orientationProvider;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
/**
* The orientation provider that delivers the current orientation from the {@link Sensor#TYPE_ROTATION_VECTOR Android
* Rotation Vector sensor}.
*
* @author Alexander Pacha
*
*/
public class RotationVectorProvider extends OrientationProvider {
/**
* Initialises a new RotationVectorProvider
*
* @param sensorManager The android sensor manager
*/
public RotationVectorProvider(SensorManager sensorManager) {
super(sensorManager);
//The rotation vector sensor that is being used for this provider to get device orientation
sensorList.add(sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR));
}
@Override
public void onSensorChanged(SensorEvent event) {
// we received a sensor event. it is a good practice to check
// that we received the proper event
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
// convert the rotation-vector to a 4x4 matrix. the matrix
// is interpreted by Open GL as the inverse of the
// rotation-vector, which is what we want.
SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix, event.values);
// Get Quaternion
float[] q = new float[4];
// Calculate angle. Starting with API_18, Android will provide this value as event.values[3], but if not, we have to calculate it manually.
SensorManager.getQuaternionFromVector(q, event.values);
currentOrientationQuaternion.setXYZW(q[1], q[2], q[3], -q[0]);
}
}
}

View File

@@ -0,0 +1,26 @@
package org.hitlabnz.sensor_fusion_demo.representation;
public class EulerAngles {
private float yaw;
private float pitch;
private float roll;
public EulerAngles(float yaw, float pitch, float roll) {
this.yaw = yaw;
this.pitch = pitch;
this.roll = roll;
}
public float getYaw() {
return yaw;
}
public float getPitch() {
return pitch;
}
public float getRoll() {
return roll;
}
}

View File

@@ -0,0 +1,824 @@
package org.hitlabnz.sensor_fusion_demo.representation;
/**
* Matrix math utilities. These methods operate on OpenGL ES format
* matrices and vectors stored in float arrays.
*
* Matrices are 4 x 4 column-vector matrices stored in column-major
* order:
*
* <pre>
* m[offset + 0] m[offset + 4] m[offset + 8] m[offset + 12]
* m[offset + 1] m[offset + 5] m[offset + 9] m[offset + 13]
* m[offset + 2] m[offset + 6] m[offset + 10] m[offset + 14]
* m[offset + 3] m[offset + 7] m[offset + 11] m[offset + 15]
* </pre>
*
* Vectors are 4 row x 1 column column-vectors stored in order:
*
* <pre>
* v[offset + 0]
* v[offset + 1]
* v[offset + 2]
* v[offset + 3]
* </pre>
*
*/
public class Matrix {
/**
* Temporary memory for operations that need temporary matrix data.
*/
private static final float[] TEMP_MATRIX_ARRAY = new float[32];
/**
* Multiply two 4x4 matrices together and store the result in a third 4x4
* matrix. In matrix notation: result = lhs x rhs. Due to the way
* matrix multiplication works, the result matrix will have the same
* effect as first multiplying by the rhs matrix, then multiplying by
* the lhs matrix. This is the opposite of what you might expect.
*
* The same float array may be passed for result, lhs, and/or rhs. However,
* the result element values are undefined if the result elements overlap
* either the lhs or rhs elements.
*
* @param result The float array that holds the result.
* @param resultOffset The offset into the result array where the result is
* stored.
* @param lhs The float array that holds the left-hand-side matrix.
* @param lhsOffset The offset into the lhs array where the lhs is stored
* @param rhs The float array that holds the right-hand-side matrix.
* @param rhsOffset The offset into the rhs array where the rhs is stored.
*
* @throws IllegalArgumentException if result, lhs, or rhs are null, or if
* resultOffset + 16 > result.length or lhsOffset + 16 > lhs.length or
* rhsOffset + 16 > rhs.length.
*/
/**
* public static void multiplyMM(float[] result, int resultOffset,
* float[] lhs, int lhsOffset, float[] rhs, int rhsOffset){
* android.opengl.Matrix.multiplyMM(result, resultOffset, lhs, lhsOffset, rhs, rhsOffset);
* }
*/
public static void multiplyMM(float[] output, int outputOffset, float[] lhs, int lhsOffset, float[] rhs,
int rhsOffset) {
//for(int i = 0; i < 4; i++){
// for(int j = 0; j < 4; j++){
// int k = i * 4;
// output[outputOffset + 0 + j] += lhs[lhsOffset + k + j] * rhs[rhsOffset + 0 * 4 + i];
// output[outputOffset + 1 * 4 + j] += lhs[lhsOffset +k + j] * rhs[rhsOffset + 1 * 4 + i];
// output[outputOffset + 2 * 4 + j] += lhs[lhsOffset +k + j] * rhs[rhsOffset + 2 * 4 + i];
// output[outputOffset + 3 * 4 + j] += lhs[lhsOffset +k + j] * rhs[rhsOffset + 3 * 4 + i];
// }
//}
output[outputOffset + 0] = lhs[lhsOffset + 0] * rhs[rhsOffset + 0] + lhs[lhsOffset + 4] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 8] * rhs[rhsOffset + 2] + lhs[lhsOffset + 12] * rhs[rhsOffset + 3];
output[outputOffset + 1] = lhs[lhsOffset + 1] * rhs[rhsOffset + 0] + lhs[lhsOffset + 5] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 9] * rhs[rhsOffset + 2] + lhs[lhsOffset + 13] * rhs[rhsOffset + 3];
output[outputOffset + 2] = lhs[lhsOffset + 2] * rhs[rhsOffset + 0] + lhs[lhsOffset + 6] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 10] * rhs[rhsOffset + 2] + lhs[lhsOffset + 14] * rhs[rhsOffset + 3];
output[outputOffset + 3] = lhs[lhsOffset + 3] * rhs[rhsOffset + 0] + lhs[lhsOffset + 7] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 11] * rhs[rhsOffset + 2] + lhs[lhsOffset + 15] * rhs[rhsOffset + 3];
output[outputOffset + 4] = lhs[lhsOffset + 0] * rhs[rhsOffset + 4] + lhs[lhsOffset + 4] * rhs[rhsOffset + 5]
+ lhs[lhsOffset + 8] * rhs[rhsOffset + 6] + lhs[lhsOffset + 12] * rhs[rhsOffset + 7];
output[outputOffset + 5] = lhs[lhsOffset + 1] * rhs[rhsOffset + 4] + lhs[lhsOffset + 5] * rhs[rhsOffset + 5]
+ lhs[lhsOffset + 9] * rhs[rhsOffset + 6] + lhs[lhsOffset + 13] * rhs[rhsOffset + 7];
output[outputOffset + 6] = lhs[lhsOffset + 2] * rhs[rhsOffset + 4] + lhs[lhsOffset + 6] * rhs[rhsOffset + 5]
+ lhs[lhsOffset + 10] * rhs[rhsOffset + 6] + lhs[lhsOffset + 14] * rhs[rhsOffset + 7];
output[outputOffset + 7] = lhs[lhsOffset + 3] * rhs[rhsOffset + 4] + lhs[lhsOffset + 7] * rhs[rhsOffset + 5]
+ lhs[lhsOffset + 11] * rhs[rhsOffset + 6] + lhs[lhsOffset + 15] * rhs[rhsOffset + 7];
output[outputOffset + 8] = lhs[lhsOffset + 0] * rhs[rhsOffset + 8] + lhs[lhsOffset + 4] * rhs[rhsOffset + 9]
+ lhs[lhsOffset + 8] * rhs[rhsOffset + 10] + lhs[lhsOffset + 12] * rhs[rhsOffset + 11];
output[outputOffset + 9] = lhs[lhsOffset + 1] * rhs[rhsOffset + 8] + lhs[lhsOffset + 5] * rhs[rhsOffset + 9]
+ lhs[lhsOffset + 9] * rhs[rhsOffset + 10] + lhs[lhsOffset + 13] * rhs[rhsOffset + 11];
output[outputOffset + 10] = lhs[lhsOffset + 2] * rhs[rhsOffset + 8] + lhs[lhsOffset + 6] * rhs[rhsOffset + 9]
+ lhs[lhsOffset + 10] * rhs[rhsOffset + 10] + lhs[lhsOffset + 14] * rhs[rhsOffset + 11];
output[outputOffset + 11] = lhs[lhsOffset + 3] * rhs[rhsOffset + 8] + lhs[lhsOffset + 7] * rhs[rhsOffset + 9]
+ lhs[lhsOffset + 11] * rhs[rhsOffset + 10] + lhs[lhsOffset + 15] * rhs[rhsOffset + 11];
output[outputOffset + 12] = lhs[lhsOffset + 0] * rhs[rhsOffset + 12] + lhs[lhsOffset + 4] * rhs[rhsOffset + 13]
+ lhs[lhsOffset + 8] * rhs[rhsOffset + 14] + lhs[lhsOffset + 12] * rhs[rhsOffset + 15];
output[outputOffset + 13] = lhs[lhsOffset + 1] * rhs[rhsOffset + 12] + lhs[lhsOffset + 5] * rhs[rhsOffset + 13]
+ lhs[lhsOffset + 9] * rhs[rhsOffset + 14] + lhs[lhsOffset + 13] * rhs[rhsOffset + 15];
output[outputOffset + 14] = lhs[lhsOffset + 2] * rhs[rhsOffset + 12] + lhs[lhsOffset + 6] * rhs[rhsOffset + 13]
+ lhs[lhsOffset + 10] * rhs[rhsOffset + 14] + lhs[lhsOffset + 14] * rhs[rhsOffset + 15];
output[outputOffset + 15] = lhs[lhsOffset + 3] * rhs[rhsOffset + 12] + lhs[lhsOffset + 7] * rhs[rhsOffset + 13]
+ lhs[lhsOffset + 11] * rhs[rhsOffset + 14] + lhs[lhsOffset + 15] * rhs[rhsOffset + 15];
}
public static void multiplyMM(float[] output, float[] lhs, float[] rhs) {
output[0] = lhs[0] * rhs[0] + lhs[4] * rhs[1] + lhs[8] * rhs[2] + lhs[12] * rhs[3];
output[1] = lhs[1] * rhs[0] + lhs[5] * rhs[1] + lhs[9] * rhs[2] + lhs[13] * rhs[3];
output[2] = lhs[2] * rhs[0] + lhs[6] * rhs[1] + lhs[10] * rhs[2] + lhs[14] * rhs[3];
output[3] = lhs[3] * rhs[0] + lhs[7] * rhs[1] + lhs[11] * rhs[2] + lhs[15] * rhs[3];
output[4] = lhs[0] * rhs[4] + lhs[4] * rhs[5] + lhs[8] * rhs[6] + lhs[12] * rhs[7];
output[5] = lhs[1] * rhs[4] + lhs[5] * rhs[5] + lhs[9] * rhs[6] + lhs[13] * rhs[7];
output[6] = lhs[2] * rhs[4] + lhs[6] * rhs[5] + lhs[10] * rhs[6] + lhs[14] * rhs[7];
output[7] = lhs[3] * rhs[4] + lhs[7] * rhs[5] + lhs[11] * rhs[6] + lhs[15] * rhs[7];
output[8] = lhs[0] * rhs[8] + lhs[4] * rhs[9] + lhs[8] * rhs[10] + lhs[12] * rhs[11];
output[9] = lhs[1] * rhs[8] + lhs[5] * rhs[9] + lhs[9] * rhs[10] + lhs[13] * rhs[11];
output[10] = lhs[2] * rhs[8] + lhs[6] * rhs[9] + lhs[10] * rhs[10] + lhs[14] * rhs[11];
output[11] = lhs[3] * rhs[8] + lhs[7] * rhs[9] + lhs[11] * rhs[10] + lhs[15] * rhs[11];
output[12] = lhs[0] * rhs[12] + lhs[4] * rhs[13] + lhs[8] * rhs[14] + lhs[12] * rhs[15];
output[13] = lhs[1] * rhs[12] + lhs[5] * rhs[13] + lhs[9] * rhs[14] + lhs[13] * rhs[15];
output[14] = lhs[2] * rhs[12] + lhs[6] * rhs[13] + lhs[10] * rhs[14] + lhs[14] * rhs[15];
output[15] = lhs[3] * rhs[12] + lhs[7] * rhs[13] + lhs[11] * rhs[14] + lhs[15] * rhs[15];
}
/**
* Multiply a 4 element vector by a 4x4 matrix and store the result in a 4
* element column vector. In matrix notation: result = lhs x rhs
*
* The same float array may be passed for resultVec, lhsMat, and/or rhsVec.
* However, the resultVec element values are undefined if the resultVec
* elements overlap either the lhsMat or rhsVec elements.
*
* @param resultVec The float array that holds the result vector.
* @param resultVecOffset The offset into the result array where the result
* vector is stored.
* @param lhsMat The float array that holds the left-hand-side matrix.
* @param lhsMatOffset The offset into the lhs array where the lhs is stored
* @param rhsVec The float array that holds the right-hand-side vector.
* @param rhsVecOffset The offset into the rhs vector where the rhs vector
* is stored.
*
* @throws IllegalArgumentException if resultVec, lhsMat,
* or rhsVec are null, or if resultVecOffset + 4 > resultVec.length
* or lhsMatOffset + 16 > lhsMat.length or
* rhsVecOffset + 4 > rhsVec.length.
*/
/* public static void multiplyMV(float[] resultVec,
* int resultVecOffset, float[] lhsMat, int lhsMatOffset,
* float[] rhsVec, int rhsVecOffset){
* android.opengl.Matrix.multiplyMV(resultVec, resultVecOffset, lhsMat, lhsMatOffset, rhsVec, rhsVecOffset);
* } */
public static void multiplyMV(float[] output, int outputOffset, float[] lhs, int lhsOffset, float[] rhs,
int rhsOffset) {
/* wrong implementation (this is for row major matrices)
* output[outputOffset +0] = lhs[lhsOffset + 0] * rhs[rhsOffset + 0] + lhs[lhsOffset + 1] * rhs[rhsOffset + 1]
* + lhs[lhsOffset + 2] * rhs[rhsOffset + 2] + lhs[lhsOffset + 3] * rhs[rhsOffset + 3];
* output[outputOffset +1] = lhs[lhsOffset + 4] * rhs[rhsOffset + 0] + lhs[lhsOffset + 5] * rhs[rhsOffset + 1] +
* lhs[lhsOffset + 6] * rhs[rhsOffset + 2] + lhs[lhsOffset + 7] * rhs[rhsOffset + 3];
* output[outputOffset +2] = lhs[lhsOffset + 8] * rhs[rhsOffset + 0] + lhs[lhsOffset + 9] * rhs[rhsOffset + 1] +
* lhs[lhsOffset + 10] * rhs[rhsOffset + 2] + lhs[lhsOffset + 11] * rhs[rhsOffset + 3];
* output[outputOffset +3] = lhs[lhsOffset + 12] * rhs[rhsOffset + 0] + lhs[lhsOffset + 13] * rhs[rhsOffset + 1]
* + lhs[lhsOffset + 14] * rhs[rhsOffset + 2] + lhs[lhsOffset + 15] * rhs[rhsOffset + 3]; */
// correct implementation for column major matrices (which is for OpenGL)
output[outputOffset + 0] = lhs[lhsOffset + 0] * rhs[rhsOffset + 0] + lhs[lhsOffset + 4] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 8] * rhs[rhsOffset + 2] + lhs[lhsOffset + 12] * rhs[rhsOffset + 3];
output[outputOffset + 1] = lhs[lhsOffset + 1] * rhs[rhsOffset + 0] + lhs[lhsOffset + 5] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 9] * rhs[rhsOffset + 2] + lhs[lhsOffset + 13] * rhs[rhsOffset + 3];
output[outputOffset + 2] = lhs[lhsOffset + 2] * rhs[rhsOffset + 0] + lhs[lhsOffset + 6] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 10] * rhs[rhsOffset + 2] + lhs[lhsOffset + 14] * rhs[rhsOffset + 3];
output[outputOffset + 3] = lhs[lhsOffset + 3] * rhs[rhsOffset + 0] + lhs[lhsOffset + 7] * rhs[rhsOffset + 1]
+ lhs[lhsOffset + 11] * rhs[rhsOffset + 2] + lhs[lhsOffset + 15] * rhs[rhsOffset + 3];
}
public static void multiplyMV(float[] outputV, float[] inputM, float[] inputV) {
outputV[0] = inputM[0] * inputV[0] + inputM[4] * inputV[1] + inputM[8] * inputV[2] + inputM[12] * inputV[3];
outputV[1] = inputM[1] * inputV[0] + inputM[5] * inputV[1] + inputM[9] * inputV[2] + inputM[13] * inputV[3];
outputV[2] = inputM[2] * inputV[0] + inputM[6] * inputV[1] + inputM[10] * inputV[2] + inputM[14] * inputV[3];
outputV[3] = inputM[3] * inputV[0] + inputM[7] * inputV[1] + inputM[11] * inputV[2] + inputM[15] * inputV[3];
}
public static void multiplyMV3(float[] outputV, float[] inputM, float[] inputV, float w) {
outputV[0] = inputM[0] * inputV[0] + inputM[4] * inputV[1] + inputM[8] * inputV[2] + inputM[12] * w;
outputV[1] = inputM[1] * inputV[0] + inputM[5] * inputV[1] + inputM[9] * inputV[2] + inputM[13] * w;
outputV[2] = inputM[2] * inputV[0] + inputM[6] * inputV[1] + inputM[10] * inputV[2] + inputM[14] * w;
}
/**
* Transposes a 4 x 4 matrix.
*
* @param mTrans the array that holds the output inverted matrix
* @param mTransOffset an offset into mInv where the inverted matrix is
* stored.
* @param m the input array
* @param mOffset an offset into m where the matrix is stored.
*/
public static void transposeM(float[] mTrans, int mTransOffset, float[] m, int mOffset) {
for (int i = 0; i < 4; i++) {
int mBase = i * 4 + mOffset;
mTrans[i + mTransOffset] = m[mBase];
mTrans[i + 4 + mTransOffset] = m[mBase + 1];
mTrans[i + 8 + mTransOffset] = m[mBase + 2];
mTrans[i + 12 + mTransOffset] = m[mBase + 3];
}
}
/**
* Inverts a 4 x 4 matrix.
*
* @param mInv the array that holds the output inverted matrix
* @param mInvOffset an offset into mInv where the inverted matrix is
* stored.
* @param m the input array
* @param mOffset an offset into m where the matrix is stored.
* @return true if the matrix could be inverted, false if it could not.
*/
public static boolean invertM(float[] mInv, int mInvOffset, float[] m, int mOffset) {
// Invert a 4 x 4 matrix using Cramer's Rule
// transpose matrix
final float src0 = m[mOffset + 0];
final float src4 = m[mOffset + 1];
final float src8 = m[mOffset + 2];
final float src12 = m[mOffset + 3];
final float src1 = m[mOffset + 4];
final float src5 = m[mOffset + 5];
final float src9 = m[mOffset + 6];
final float src13 = m[mOffset + 7];
final float src2 = m[mOffset + 8];
final float src6 = m[mOffset + 9];
final float src10 = m[mOffset + 10];
final float src14 = m[mOffset + 11];
final float src3 = m[mOffset + 12];
final float src7 = m[mOffset + 13];
final float src11 = m[mOffset + 14];
final float src15 = m[mOffset + 15];
// calculate pairs for first 8 elements (cofactors)
final float atmp0 = src10 * src15;
final float atmp1 = src11 * src14;
final float atmp2 = src9 * src15;
final float atmp3 = src11 * src13;
final float atmp4 = src9 * src14;
final float atmp5 = src10 * src13;
final float atmp6 = src8 * src15;
final float atmp7 = src11 * src12;
final float atmp8 = src8 * src14;
final float atmp9 = src10 * src12;
final float atmp10 = src8 * src13;
final float atmp11 = src9 * src12;
// calculate first 8 elements (cofactors)
final float dst0 = (atmp0 * src5 + atmp3 * src6 + atmp4 * src7) - (atmp1 * src5 + atmp2 * src6 + atmp5 * src7);
final float dst1 = (atmp1 * src4 + atmp6 * src6 + atmp9 * src7) - (atmp0 * src4 + atmp7 * src6 + atmp8 * src7);
final float dst2 = (atmp2 * src4 + atmp7 * src5 + atmp10 * src7)
- (atmp3 * src4 + atmp6 * src5 + atmp11 * src7);
final float dst3 = (atmp5 * src4 + atmp8 * src5 + atmp11 * src6)
- (atmp4 * src4 + atmp9 * src5 + atmp10 * src6);
final float dst4 = (atmp1 * src1 + atmp2 * src2 + atmp5 * src3) - (atmp0 * src1 + atmp3 * src2 + atmp4 * src3);
final float dst5 = (atmp0 * src0 + atmp7 * src2 + atmp8 * src3) - (atmp1 * src0 + atmp6 * src2 + atmp9 * src3);
final float dst6 = (atmp3 * src0 + atmp6 * src1 + atmp11 * src3)
- (atmp2 * src0 + atmp7 * src1 + atmp10 * src3);
final float dst7 = (atmp4 * src0 + atmp9 * src1 + atmp10 * src2)
- (atmp5 * src0 + atmp8 * src1 + atmp11 * src2);
// calculate pairs for second 8 elements (cofactors)
final float btmp0 = src2 * src7;
final float btmp1 = src3 * src6;
final float btmp2 = src1 * src7;
final float btmp3 = src3 * src5;
final float btmp4 = src1 * src6;
final float btmp5 = src2 * src5;
final float btmp6 = src0 * src7;
final float btmp7 = src3 * src4;
final float btmp8 = src0 * src6;
final float btmp9 = src2 * src4;
final float btmp10 = src0 * src5;
final float btmp11 = src1 * src4;
// calculate second 8 elements (cofactors)
final float dst8 = (btmp0 * src13 + btmp3 * src14 + btmp4 * src15)
- (btmp1 * src13 + btmp2 * src14 + btmp5 * src15);
final float dst9 = (btmp1 * src12 + btmp6 * src14 + btmp9 * src15)
- (btmp0 * src12 + btmp7 * src14 + btmp8 * src15);
final float dst10 = (btmp2 * src12 + btmp7 * src13 + btmp10 * src15)
- (btmp3 * src12 + btmp6 * src13 + btmp11 * src15);
final float dst11 = (btmp5 * src12 + btmp8 * src13 + btmp11 * src14)
- (btmp4 * src12 + btmp9 * src13 + btmp10 * src14);
final float dst12 = (btmp2 * src10 + btmp5 * src11 + btmp1 * src9)
- (btmp4 * src11 + btmp0 * src9 + btmp3 * src10);
final float dst13 = (btmp8 * src11 + btmp0 * src8 + btmp7 * src10)
- (btmp6 * src10 + btmp9 * src11 + btmp1 * src8);
final float dst14 = (btmp6 * src9 + btmp11 * src11 + btmp3 * src8)
- (btmp10 * src11 + btmp2 * src8 + btmp7 * src9);
final float dst15 = (btmp10 * src10 + btmp4 * src8 + btmp9 * src9)
- (btmp8 * src9 + btmp11 * src10 + btmp5 * src8);
// calculate determinant
final float det = src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3;
if (det == 0.0f) {
return false;
}
// calculate matrix inverse
final float invdet = 1.0f / det;
mInv[mInvOffset] = dst0 * invdet;
mInv[1 + mInvOffset] = dst1 * invdet;
mInv[2 + mInvOffset] = dst2 * invdet;
mInv[3 + mInvOffset] = dst3 * invdet;
mInv[4 + mInvOffset] = dst4 * invdet;
mInv[5 + mInvOffset] = dst5 * invdet;
mInv[6 + mInvOffset] = dst6 * invdet;
mInv[7 + mInvOffset] = dst7 * invdet;
mInv[8 + mInvOffset] = dst8 * invdet;
mInv[9 + mInvOffset] = dst9 * invdet;
mInv[10 + mInvOffset] = dst10 * invdet;
mInv[11 + mInvOffset] = dst11 * invdet;
mInv[12 + mInvOffset] = dst12 * invdet;
mInv[13 + mInvOffset] = dst13 * invdet;
mInv[14 + mInvOffset] = dst14 * invdet;
mInv[15 + mInvOffset] = dst15 * invdet;
return true;
}
/**
* Computes an orthographic projection matrix.
*
* @param m returns the result
* @param mOffset
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near,
float far) {
if (left == right) {
throw new IllegalArgumentException("left == right");
}
if (bottom == top) {
throw new IllegalArgumentException("bottom == top");
}
if (near == far) {
throw new IllegalArgumentException("near == far");
}
final float r_width = 1.0f / (right - left);
final float r_height = 1.0f / (top - bottom);
final float r_depth = 1.0f / (far - near);
final float x = 2.0f * (r_width);
final float y = 2.0f * (r_height);
final float z = -2.0f * (r_depth);
final float tx = -(right + left) * r_width;
final float ty = -(top + bottom) * r_height;
final float tz = -(far + near) * r_depth;
m[mOffset + 0] = x;
m[mOffset + 5] = y;
m[mOffset + 10] = z;
m[mOffset + 12] = tx;
m[mOffset + 13] = ty;
m[mOffset + 14] = tz;
m[mOffset + 15] = 1.0f;
m[mOffset + 1] = 0.0f;
m[mOffset + 2] = 0.0f;
m[mOffset + 3] = 0.0f;
m[mOffset + 4] = 0.0f;
m[mOffset + 6] = 0.0f;
m[mOffset + 7] = 0.0f;
m[mOffset + 8] = 0.0f;
m[mOffset + 9] = 0.0f;
m[mOffset + 11] = 0.0f;
}
/**
* Define a projection matrix in terms of six clip planes
*
* @param m the float array that holds the perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near,
float far) {
if (left == right) {
throw new IllegalArgumentException("left == right");
}
if (top == bottom) {
throw new IllegalArgumentException("top == bottom");
}
if (near == far) {
throw new IllegalArgumentException("near == far");
}
if (near <= 0.0f) {
throw new IllegalArgumentException("near <= 0.0f");
}
if (far <= 0.0f) {
throw new IllegalArgumentException("far <= 0.0f");
}
final float r_width = 1.0f / (right - left);
final float r_height = 1.0f / (top - bottom);
final float r_depth = 1.0f / (near - far);
final float x = 2.0f * (near * r_width);
final float y = 2.0f * (near * r_height);
final float A = 2.0f * ((right + left) * r_width);
final float B = (top + bottom) * r_height;
final float C = (far + near) * r_depth;
final float D = 2.0f * (far * near * r_depth);
m[offset + 0] = x;
m[offset + 5] = y;
m[offset + 8] = A;
m[offset + 9] = B;
m[offset + 10] = C;
m[offset + 14] = D;
m[offset + 11] = -1.0f;
m[offset + 1] = 0.0f;
m[offset + 2] = 0.0f;
m[offset + 3] = 0.0f;
m[offset + 4] = 0.0f;
m[offset + 6] = 0.0f;
m[offset + 7] = 0.0f;
m[offset + 12] = 0.0f;
m[offset + 13] = 0.0f;
m[offset + 15] = 0.0f;
}
/**
* Define a projection matrix in terms of a field of view angle, an
* aspect ratio, and z clip planes
*
* @param m the float array that holds the perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param fovy field of view in y direction, in degrees
* @param aspect width to height aspect ratio of the viewport
* @param zNear
* @param zFar
*/
public static void perspectiveM(float[] m, int offset, float fovy, float aspect, float zNear, float zFar) {
float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0));
float rangeReciprocal = 1.0f / (zNear - zFar);
m[offset + 0] = f / aspect;
m[offset + 1] = 0.0f;
m[offset + 2] = 0.0f;
m[offset + 3] = 0.0f;
m[offset + 4] = 0.0f;
m[offset + 5] = f;
m[offset + 6] = 0.0f;
m[offset + 7] = 0.0f;
m[offset + 8] = 0.0f;
m[offset + 9] = 0.0f;
m[offset + 10] = (zFar + zNear) * rangeReciprocal;
m[offset + 11] = -1.0f;
m[offset + 12] = 0.0f;
m[offset + 13] = 0.0f;
m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal;
m[offset + 15] = 0.0f;
}
/**
* Computes the length of a vector
*
* @param x x coordinate of a vector
* @param y y coordinate of a vector
* @param z z coordinate of a vector
* @return the length of a vector
*/
public static float length(float x, float y, float z) {
return (float) Math.sqrt(x * x + y * y + z * z);
}
/**
* Sets matrix m to the identity matrix.
*
* @param sm returns the result
* @param smOffset index into sm where the result matrix starts
*/
public static void setIdentityM(float[] sm, int smOffset) {
for (int i = 0; i < 16; i++) {
sm[smOffset + i] = 0;
}
for (int i = 0; i < 16; i += 5) {
sm[smOffset + i] = 1.0f;
}
}
/**
* Scales matrix m by x, y, and z, putting the result in sm
*
* @param sm returns the result
* @param smOffset index into sm where the result matrix starts
* @param m source matrix
* @param mOffset index into m where the source matrix starts
* @param x scale factor x
* @param y scale factor y
* @param z scale factor z
*/
public static void scaleM(float[] sm, int smOffset, float[] m, int mOffset, float x, float y, float z) {
for (int i = 0; i < 4; i++) {
int smi = smOffset + i;
int mi = mOffset + i;
sm[smi] = m[mi] * x;
sm[4 + smi] = m[4 + mi] * y;
sm[8 + smi] = m[8 + mi] * z;
sm[12 + smi] = m[12 + mi];
}
}
/**
* Scales matrix m in place by sx, sy, and sz
*
* @param m matrix to scale
* @param mOffset index into m where the matrix starts
* @param x scale factor x
* @param y scale factor y
* @param z scale factor z
*/
public static void scaleM(float[] m, int mOffset, float x, float y, float z) {
for (int i = 0; i < 4; i++) {
int mi = mOffset + i;
m[mi] *= x;
m[4 + mi] *= y;
m[8 + mi] *= z;
}
}
/**
* Translates matrix m by x, y, and z, putting the result in tm
*
* @param tm returns the result
* @param tmOffset index into sm where the result matrix starts
* @param m source matrix
* @param mOffset index into m where the source matrix starts
* @param x translation factor x
* @param y translation factor y
* @param z translation factor z
*/
public static void translateM(float[] tm, int tmOffset, float[] m, int mOffset, float x, float y, float z) {
for (int i = 0; i < 12; i++) {
tm[tmOffset + i] = m[mOffset + i];
}
for (int i = 0; i < 4; i++) {
int tmi = tmOffset + i;
int mi = mOffset + i;
tm[12 + tmi] = m[mi] * x + m[4 + mi] * y + m[8 + mi] * z + m[12 + mi];
}
}
/**
* Translates matrix m by x, y, and z in place.
*
* @param m matrix
* @param mOffset index into m where the matrix starts
* @param x translation factor x
* @param y translation factor y
* @param z translation factor z
*/
public static void translateM(float[] m, int mOffset, float x, float y, float z) {
for (int i = 0; i < 4; i++) {
int mi = mOffset + i;
m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
}
}
/**
* Rotates matrix m by angle a (in degrees) around the axis (x, y, z)
*
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param m source matrix
* @param mOffset index into m where the source matrix starts
* @param a angle to rotate in degrees
* @param x scale factor x
* @param y scale factor y
* @param z scale factor z
*/
public static void rotateM(float[] rm, int rmOffset, float[] m, int mOffset, float a, float x, float y, float z) {
synchronized (TEMP_MATRIX_ARRAY) {
setRotateM(TEMP_MATRIX_ARRAY, 0, a, x, y, z);
multiplyMM(rm, rmOffset, m, mOffset, TEMP_MATRIX_ARRAY, 0);
}
}
/**
* Rotates matrix m in place by angle a (in degrees)
* around the axis (x, y, z)
*
* @param m source matrix
* @param mOffset index into m where the matrix starts
* @param a angle to rotate in degrees
* @param x scale factor x
* @param y scale factor y
* @param z scale factor z
*/
public static void rotateM(float[] m, int mOffset, float a, float x, float y, float z) {
synchronized (TEMP_MATRIX_ARRAY) {
setRotateM(TEMP_MATRIX_ARRAY, 0, a, x, y, z);
multiplyMM(TEMP_MATRIX_ARRAY, 16, m, mOffset, TEMP_MATRIX_ARRAY, 0);
System.arraycopy(TEMP_MATRIX_ARRAY, 16, m, mOffset, 16);
}
}
/**
* Rotates matrix m by angle a (in degrees) around the axis (x, y, z)
*
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param a angle to rotate in degrees
* @param x scale factor x
* @param y scale factor y
* @param z scale factor z
*/
public static void setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z) {
rm[rmOffset + 3] = 0;
rm[rmOffset + 7] = 0;
rm[rmOffset + 11] = 0;
rm[rmOffset + 12] = 0;
rm[rmOffset + 13] = 0;
rm[rmOffset + 14] = 0;
rm[rmOffset + 15] = 1;
a *= (float) (Math.PI / 180.0f);
float s = (float) Math.sin(a);
float c = (float) Math.cos(a);
if (1.0f == x && 0.0f == y && 0.0f == z) {
rm[rmOffset + 5] = c;
rm[rmOffset + 10] = c;
rm[rmOffset + 6] = s;
rm[rmOffset + 9] = -s;
rm[rmOffset + 1] = 0;
rm[rmOffset + 2] = 0;
rm[rmOffset + 4] = 0;
rm[rmOffset + 8] = 0;
rm[rmOffset + 0] = 1;
} else if (0.0f == x && 1.0f == y && 0.0f == z) {
rm[rmOffset + 0] = c;
rm[rmOffset + 10] = c;
rm[rmOffset + 8] = s;
rm[rmOffset + 2] = -s;
rm[rmOffset + 1] = 0;
rm[rmOffset + 4] = 0;
rm[rmOffset + 6] = 0;
rm[rmOffset + 9] = 0;
rm[rmOffset + 5] = 1;
} else if (0.0f == x && 0.0f == y && 1.0f == z) {
rm[rmOffset + 0] = c;
rm[rmOffset + 5] = c;
rm[rmOffset + 1] = s;
rm[rmOffset + 4] = -s;
rm[rmOffset + 2] = 0;
rm[rmOffset + 6] = 0;
rm[rmOffset + 8] = 0;
rm[rmOffset + 9] = 0;
rm[rmOffset + 10] = 1;
} else {
float len = length(x, y, z);
if (1.0f != len) {
float recipLen = 1.0f / len;
x *= recipLen;
y *= recipLen;
z *= recipLen;
}
float nc = 1.0f - c;
float xy = x * y;
float yz = y * z;
float zx = z * x;
float xs = x * s;
float ys = y * s;
float zs = z * s;
rm[rmOffset + 0] = x * x * nc + c;
rm[rmOffset + 4] = xy * nc - zs;
rm[rmOffset + 8] = zx * nc + ys;
rm[rmOffset + 1] = xy * nc + zs;
rm[rmOffset + 5] = y * y * nc + c;
rm[rmOffset + 9] = yz * nc - xs;
rm[rmOffset + 2] = zx * nc - ys;
rm[rmOffset + 6] = yz * nc + xs;
rm[rmOffset + 10] = z * z * nc + c;
}
}
/**
* Converts Euler angles to a rotation matrix
*
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param x angle of rotation, in degrees
* @param y angle of rotation, in degrees
* @param z angle of rotation, in degrees
*/
public static void setRotateEulerM(float[] rm, int rmOffset, float x, float y, float z) {
x *= (float) (Math.PI / 180.0f);
y *= (float) (Math.PI / 180.0f);
z *= (float) (Math.PI / 180.0f);
float cx = (float) Math.cos(x);
float sx = (float) Math.sin(x);
float cy = (float) Math.cos(y);
float sy = (float) Math.sin(y);
float cz = (float) Math.cos(z);
float sz = (float) Math.sin(z);
float cxsy = cx * sy;
float sxsy = sx * sy;
rm[rmOffset + 0] = cy * cz;
rm[rmOffset + 1] = -cy * sz;
rm[rmOffset + 2] = sy;
rm[rmOffset + 3] = 0.0f;
rm[rmOffset + 4] = cxsy * cz + cx * sz;
rm[rmOffset + 5] = -cxsy * sz + cx * cz;
rm[rmOffset + 6] = -sx * cy;
rm[rmOffset + 7] = 0.0f;
rm[rmOffset + 8] = -sxsy * cz + sx * sz;
rm[rmOffset + 9] = sxsy * sz + sx * cz;
rm[rmOffset + 10] = cx * cy;
rm[rmOffset + 11] = 0.0f;
rm[rmOffset + 12] = 0.0f;
rm[rmOffset + 13] = 0.0f;
rm[rmOffset + 14] = 0.0f;
rm[rmOffset + 15] = 1.0f;
}
/**
* Define a viewing transformation in terms of an eye point, a center of
* view, and an up vector.
*
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param eyeX eye point X
* @param eyeY eye point Y
* @param eyeZ eye point Z
* @param centerX center of view X
* @param centerY center of view Y
* @param centerZ center of view Z
* @param upX up vector X
* @param upY up vector Y
* @param upZ up vector Z
*/
public static void setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX,
float centerY, float centerZ, float upX, float upY, float upZ) {
// See the OpenGL GLUT documentation for gluLookAt for a description
// of the algorithm. We implement it in a straightforward way:
float fx = centerX - eyeX;
float fy = centerY - eyeY;
float fz = centerZ - eyeZ;
// Normalize f
float rlf = 1.0f / Matrix.length(fx, fy, fz);
fx *= rlf;
fy *= rlf;
fz *= rlf;
// compute s = f x up (x means "cross product")
float sx = fy * upZ - fz * upY;
float sy = fz * upX - fx * upZ;
float sz = fx * upY - fy * upX;
// and normalize s
float rls = 1.0f / Matrix.length(sx, sy, sz);
sx *= rls;
sy *= rls;
sz *= rls;
// compute u = s x f
float ux = sy * fz - sz * fy;
float uy = sz * fx - sx * fz;
float uz = sx * fy - sy * fx;
rm[rmOffset + 0] = sx;
rm[rmOffset + 1] = ux;
rm[rmOffset + 2] = -fx;
rm[rmOffset + 3] = 0.0f;
rm[rmOffset + 4] = sy;
rm[rmOffset + 5] = uy;
rm[rmOffset + 6] = -fy;
rm[rmOffset + 7] = 0.0f;
rm[rmOffset + 8] = sz;
rm[rmOffset + 9] = uz;
rm[rmOffset + 10] = -fz;
rm[rmOffset + 11] = 0.0f;
rm[rmOffset + 12] = 0.0f;
rm[rmOffset + 13] = 0.0f;
rm[rmOffset + 14] = 0.0f;
rm[rmOffset + 15] = 1.0f;
translateM(rm, rmOffset, -eyeX, -eyeY, -eyeZ);
}
}

View File

@@ -0,0 +1,512 @@
package org.hitlabnz.sensor_fusion_demo.representation;
import android.util.Log;
/**
* The Class Matrixf4x4.
*
* Internal the matrix is structured as
*
* [ x0 , y0 , z0 , w0 ] [ x1 , y1 , z1 , w1 ] [ x2 , y2 , z2 , w2 ] [ x3 , y3 , z3 , w3 ]
*
* it is recommend that when setting the matrix values individually that you use the set{x,#} methods, where 'x' is
* 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
* because it will map directly to that part of the matrix regardless of whether or not the internal matrix is column
* 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.
* 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
* error.
*
*/
public class Matrixf4x4 {
public static final int[] matIndCol9_3x3 = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
public static final int[] matIndCol16_3x3 = { 0, 1, 2, 4, 5, 6, 8, 9, 10 };
public static final int[] matIndRow9_3x3 = { 0, 3, 6, 1, 4, 7, 3, 5, 8 };
public static final int[] matIndRow16_3x3 = { 0, 4, 8, 1, 5, 9, 2, 6, 10 };
public static final int[] matIndCol16_4x4 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
public static final int[] matIndRow16_4x4 = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 };
private boolean colMaj = true;
private boolean matrixValid = false;
/** The matrix. */
public float[] matrix;
/**
* Instantiates a new matrixf4x4. The Matrix is assumed to be Column major, however you can change this by using the
* setColumnMajor function to false and it will operate like a row major matrix.
*/
public Matrixf4x4() {
// The matrix is defined as float[column][row]
this.matrix = new float[16];
Matrix.setIdentityM(this.matrix, 0);
matrixValid = true;
}
/**
* Gets the matrix.
*
* @return the matrix, can be null if the matrix is invalid
*/
public float[] getMatrix() {
return this.matrix;
}
public int size() {
return matrix.length;
}
/**
* Sets the matrix from a float[16] array. If the matrix you set isn't 16 long then the matrix will be set as
* invalid.
*
* @param matrix the new matrix
*/
public void setMatrix(float[] matrix) {
this.matrix = matrix;
if (matrix.length == 16 || matrix.length == 9)
this.matrixValid = true;
else {
this.matrixValid = false;
Log.e("matrix", "Matrix set is invalid, size is " + matrix.length + " expected 9 or 16");
}
}
public void setMatrixValues(float[] otherMatrix) {
if (this.matrix.length != otherMatrix.length) {
Log.e("matrix", "Matrix set is invalid, size is " + otherMatrix.length + " expected 9 or 16");
}
for (int i = 0; i < otherMatrix.length; i++) {
this.matrix[i] = otherMatrix[i];
}
}
/**
* Set whether the internal data is col major by passing true, or false for a row major matrix. The matrix is column
* major by default.
*
* @param colMajor
*/
public void setColumnMajor(boolean colMajor) {
this.colMaj = colMajor;
}
/**
* Find out if the stored matrix is column major
*
* @return
*/
public boolean isColumnMajor() {
return colMaj;
}
/**
* Multiply the given vector by this matrix. This should only be used if the matrix is of size 16 (use the
* matrix.size() method).
*
* @param vector A vector of length 4.
*/
public void multiplyVector4fByMatrix(Vector4f vector) {
if (matrixValid && matrix.length == 16) {
float x = 0;
float y = 0;
float z = 0;
float w = 0;
float[] vectorArray = vector.ToArray();
if (colMaj) {
for (int i = 0; i < 4; i++) {
int k = i * 4;
x += this.matrix[k + 0] * vectorArray[i];
y += this.matrix[k + 1] * vectorArray[i];
z += this.matrix[k + 2] * vectorArray[i];
w += this.matrix[k + 3] * vectorArray[i];
}
} else {
for (int i = 0; i < 4; i++) {
x += this.matrix[0 + i] * vectorArray[i];
y += this.matrix[4 + i] * vectorArray[i];
z += this.matrix[8 + i] * vectorArray[i];
w += this.matrix[12 + i] * vectorArray[i];
}
}
vector.setX(x);
vector.setY(y);
vector.setZ(z);
vector.setW(w);
} else
Log.e("matrix", "Matrix is invalid, is " + matrix.length + " long, this equation expects a 16 value matrix");
}
/**
* Multiply the given vector by this matrix. This should only be used if the matrix is of size 9 (use the
* matrix.size() method).
*
* @param vector A vector of length 3.
*/
public void multiplyVector3fByMatrix(Vector3f vector) {
if (matrixValid && matrix.length == 9) {
float x = 0;
float y = 0;
float z = 0;
float[] vectorArray = vector.toArray();
if (!colMaj) {
for (int i = 0; i < 3; i++) {
int k = i * 3;
x += this.matrix[k + 0] * vectorArray[i];
y += this.matrix[k + 1] * vectorArray[i];
z += this.matrix[k + 2] * vectorArray[i];
}
} else {
for (int i = 0; i < 3; i++) {
x += this.matrix[0 + i] * vectorArray[i];
y += this.matrix[3 + i] * vectorArray[i];
z += this.matrix[6 + i] * vectorArray[i];
}
}
vector.setX(x);
vector.setY(y);
vector.setZ(z);
} else
Log.e("matrix", "Matrix is invalid, is " + matrix.length
+ " long, this function expects the internal matrix to be of size 9");
}
public boolean isMatrixValid() {
return matrixValid;
}
/**
* Multiply matrix4x4 by matrix.
*
* @param matrixf the matrixf
*/
public void multiplyMatrix4x4ByMatrix(Matrixf4x4 matrixf) {
// TODO implement Strassen Algorithm in place of this slower naive one.
if (matrixValid && matrixf.isMatrixValid()) {
float[] bufferMatrix = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
float[] matrix = matrixf.getMatrix();
/**
* for(int i = 0; i < 4; i++){ for(int j = 0; j < 4; j++){
*
* int k = i * 4; bufferMatrix[0 + j] += this.matrix[k + j] * matrix[0 * 4 + i]; bufferMatrix[1 * 4 + j] +=
* this.matrix[k + j] * matrix[1 * 4 + i]; bufferMatrix[2 * 4 + j] += this.matrix[k + j] * matrix[2 * 4 +
* i]; bufferMatrix[3 * 4 + j] += this.matrix[k + j] * matrix[3 * 4 + i]; } }
*/
multiplyMatrix(matrix, 0, bufferMatrix, 0);
matrixf.setMatrix(bufferMatrix);
} else
Log.e("matrix", "Matrix is invalid, internal is " + matrix.length + " long" + " , input matrix is "
+ matrixf.getMatrix().length + " long");
}
public void multiplyMatrix(float[] input, int inputOffset, float[] output, int outputOffset) {
float[] bufferMatrix = output;
float[] matrix = input;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int k = i * 4;
bufferMatrix[outputOffset + 0 + j] += this.matrix[k + j] * matrix[inputOffset + 0 * 4 + i];
bufferMatrix[outputOffset + 1 * 4 + j] += this.matrix[k + j] * matrix[inputOffset + 1 * 4 + i];
bufferMatrix[outputOffset + 2 * 4 + j] += this.matrix[k + j] * matrix[inputOffset + 2 * 4 + i];
bufferMatrix[outputOffset + 3 * 4 + j] += this.matrix[k + j] * matrix[inputOffset + 3 * 4 + i];
}
}
}
/**
* This will rearrange the internal structure of the matrix. Be careful though as this is an expensive operation.
*/
public void transpose() {
if (matrixValid) {
if (this.matrix.length == 16) {
float[] newMatrix = new float[16];
for (int i = 0; i < 4; i++) {
int k = i * 4;
newMatrix[k] = matrix[i];
newMatrix[k + 1] = matrix[4 + i];
newMatrix[k + 2] = matrix[8 + i];
newMatrix[k + 3] = matrix[12 + i];
}
matrix = newMatrix;
} else {
float[] newMatrix = new float[9];
for (int i = 0; i < 3; i++) {
int k = i * 3;
newMatrix[k] = matrix[i];
newMatrix[k + 1] = matrix[3 + i];
newMatrix[k + 2] = matrix[6 + i];
}
matrix = newMatrix;
}
}
}
public void setX0(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[0]] = value;
else
matrix[matIndRow16_3x3[0]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[0]] = value;
else
matrix[matIndRow9_3x3[0]] = value;
}
}
}
public void setX1(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[1]] = value;
else
matrix[matIndRow16_3x3[1]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[1]] = value;
else
matrix[matIndRow9_3x3[1]] = value;
}
}
}
public void setX2(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[2]] = value;
else
matrix[matIndRow16_3x3[2]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[2]] = value;
else
matrix[matIndRow9_3x3[2]] = value;
}
}
}
public void setY0(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[3]] = value;
else
matrix[matIndRow16_3x3[3]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[3]] = value;
else
matrix[matIndRow9_3x3[3]] = value;
}
}
}
public void setY1(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[4]] = value;
else
matrix[matIndRow16_3x3[4]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[4]] = value;
else
matrix[matIndRow9_3x3[4]] = value;
}
}
}
public void setY2(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[5]] = value;
else
matrix[matIndRow16_3x3[5]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[5]] = value;
else
matrix[matIndRow9_3x3[5]] = value;
}
}
}
public void setZ0(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[6]] = value;
else
matrix[matIndRow16_3x3[6]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[6]] = value;
else
matrix[matIndRow9_3x3[6]] = value;
}
}
}
public void setZ1(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[7]] = value;
else
matrix[matIndRow16_3x3[7]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[7]] = value;
else
matrix[matIndRow9_3x3[7]] = value;
}
}
}
public void setZ2(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_3x3[8]] = value;
else
matrix[matIndRow16_3x3[8]] = value;
} else {
if (colMaj)
matrix[matIndCol9_3x3[8]] = value;
else
matrix[matIndRow9_3x3[8]] = value;
}
}
}
public void setX3(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[3]] = value;
else
matrix[matIndRow16_4x4[3]] = value;
}
}
}
public void setY3(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[7]] = value;
else
matrix[matIndRow16_4x4[7]] = value;
}
}
}
public void setZ3(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[11]] = value;
else
matrix[matIndRow16_4x4[11]] = value;
}
}
}
public void setW0(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[12]] = value;
else
matrix[matIndRow16_4x4[12]] = value;
}
}
}
public void setW1(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[13]] = value;
else
matrix[matIndRow16_4x4[13]] = value;
}
}
}
public void setW2(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[14]] = value;
else
matrix[matIndRow16_4x4[14]] = value;
}
}
}
public void setW3(float value) {
if (matrixValid) {
if (matrix.length == 16) {
if (colMaj)
matrix[matIndCol16_4x4[15]] = value;
else
matrix[matIndRow16_4x4[15]] = value;
}
}
}
}

View File

@@ -0,0 +1,536 @@
package org.hitlabnz.sensor_fusion_demo.representation;
/**
* The Quaternion class. A Quaternion is a four-dimensional vector that is used to represent rotations of a rigid body
* in the 3D space. It is very similar to a rotation vector; it contains an angle, encoded into the w component
* and three components to describe the rotation-axis (encoded into x, y, z).
*
* <p>
* Quaternions allow for elegant descriptions of 3D rotations, interpolations as well as extrapolations and compared to
* Euler angles, they don't suffer from gimbal lock. Interpolations between two Quaternions are called SLERP (Spherical
* Linear Interpolation).
* </p>
*
* <p>
* This class also contains the representation of the same rotation as a Quaternion and 4x4-Rotation-Matrix.
* </p>
*
* @author Leigh Beattie, Alexander Pacha
*
*/
public class Quaternion extends Vector4f {
/**
* A randomly generated UID to make the Quaternion object serialisable.
*/
private static final long serialVersionUID = -7148812599404359073L;
/**
* Rotation matrix that contains the same rotation as the Quaternion in a 4x4 homogenised rotation matrix.
* Remember that for performance reasons, this matrix is only updated, when it is accessed and not on every change
* of the quaternion-values.
*/
private Matrixf4x4 matrix;
/**
* This variable is used to synchronise the rotation matrix with the current quaternion values. If someone has
* changed the
* quaternion numbers then the matrix will need to be updated. To save on processing we only really want to update
* the matrix when someone wants to fetch it, instead of whenever someone sets a quaternion value.
*/
private boolean dirty = false;
/**
* Creates a new Quaternion object and initialises it with the identity Quaternion
*/
public Quaternion() {
super();
matrix = new Matrixf4x4();
loadIdentityQuat();
}
@Override
public Quaternion clone() {
Quaternion clone = new Quaternion();
clone.copyVec4(this);
return clone;
}
/**
* Normalise this Quaternion into a unity Quaternion.
*/
public void normalise() {
this.dirty = true;
float mag = (float) Math.sqrt(points[3] * points[3] + points[0] * points[0] + points[1] * points[1] + points[2]
* points[2]);
points[3] = points[3] / mag;
points[0] = points[0] / mag;
points[1] = points[1] / mag;
points[2] = points[2] / mag;
}
@Override
public void normalize() {
normalise();
}
/**
* Copies the values from the given quaternion to this one
*
* @param quat The quaternion to copy from
*/
public void set(Quaternion quat) {
this.dirty = true;
copyVec4(quat);
}
/**
* Multiply this quaternion by the input quaternion and store the result in the out quaternion
*
* @param input
* @param output
*/
public void multiplyByQuat(Quaternion input, Quaternion output) {
Vector4f inputCopy = new Vector4f();
if (input != output) {
output.points[3] = (points[3] * input.points[3] - points[0] * input.points[0] - points[1] * input.points[1] - points[2]
* input.points[2]); //w = w1w2 - x1x2 - y1y2 - z1z2
output.points[0] = (points[3] * input.points[0] + points[0] * input.points[3] + points[1] * input.points[2] - points[2]
* input.points[1]); //x = w1x2 + x1w2 + y1z2 - z1y2
output.points[1] = (points[3] * input.points[1] + points[1] * input.points[3] + points[2] * input.points[0] - points[0]
* input.points[2]); //y = w1y2 + y1w2 + z1x2 - x1z2
output.points[2] = (points[3] * input.points[2] + points[2] * input.points[3] + points[0] * input.points[1] - points[1]
* input.points[0]); //z = w1z2 + z1w2 + x1y2 - y1x2
} else {
inputCopy.points[0] = input.points[0];
inputCopy.points[1] = input.points[1];
inputCopy.points[2] = input.points[2];
inputCopy.points[3] = input.points[3];
output.points[3] = (points[3] * inputCopy.points[3] - points[0] * inputCopy.points[0] - points[1]
* inputCopy.points[1] - points[2] * inputCopy.points[2]); //w = w1w2 - x1x2 - y1y2 - z1z2
output.points[0] = (points[3] * inputCopy.points[0] + points[0] * inputCopy.points[3] + points[1]
* inputCopy.points[2] - points[2] * inputCopy.points[1]); //x = w1x2 + x1w2 + y1z2 - z1y2
output.points[1] = (points[3] * inputCopy.points[1] + points[1] * inputCopy.points[3] + points[2]
* inputCopy.points[0] - points[0] * inputCopy.points[2]); //y = w1y2 + y1w2 + z1x2 - x1z2
output.points[2] = (points[3] * inputCopy.points[2] + points[2] * inputCopy.points[3] + points[0]
* inputCopy.points[1] - points[1] * inputCopy.points[0]); //z = w1z2 + z1w2 + x1y2 - y1x2
}
}
/**
* Multiply this quaternion by the input quaternion and store the result in the out quaternion
*
* @param input
* @param output
*/
Quaternion bufferQuaternion;
public void multiplyByQuat(Quaternion input) {
if (bufferQuaternion == null) {
bufferQuaternion = new Quaternion();
}
this.dirty = true;
bufferQuaternion.copyVec4(this);
multiplyByQuat(input, bufferQuaternion);
this.copyVec4(bufferQuaternion);
}
/**
* Multiplies this Quaternion with a scalar
*
* @param scalar the value that the vector should be multiplied with
*/
public void multiplyByScalar(float scalar) {
this.dirty = true;
multiplyByScalar(scalar);
}
/**
* Add a quaternion to this quaternion
*
* @param input The quaternion that you want to add to this one
*/
public void addQuat(Quaternion input) {
this.dirty = true;
addQuat(input, this);
}
/**
* Add this quaternion and another quaternion together and store the result in the output quaternion
*
* @param input The quaternion you want added to this quaternion
* @param output The quaternion you want to store the output in.
*/
public void addQuat(Quaternion input, Quaternion output) {
output.setX(getX() + input.getX());
output.setY(getY() + input.getY());
output.setZ(getZ() + input.getZ());
output.setW(getW() + input.getW());
}
/**
* Subtract a quaternion to this quaternion
*
* @param input The quaternion that you want to subtracted from this one
*/
public void subQuat(Quaternion input) {
this.dirty = true;
subQuat(input, this);
}
/**
* Subtract another quaternion from this quaternion and store the result in the output quaternion
*
* @param input The quaternion you want subtracted from this quaternion
* @param output The quaternion you want to store the output in.
*/
public void subQuat(Quaternion input, Quaternion output) {
output.setX(getX() - input.getX());
output.setY(getY() - input.getY());
output.setZ(getZ() - input.getZ());
output.setW(getW() - input.getW());
}
/**
* Converts this Quaternion into the Rotation-Matrix representation which can be accessed by
* {@link Quaternion#getMatrix4x4 getMatrix4x4}
*/
private void convertQuatToMatrix() {
float x = points[0];
float y = points[1];
float z = points[2];
float w = points[3];
matrix.setX0(1 - 2 * (y * y) - 2 * (z * z)); //1 - 2y2 - 2z2
matrix.setX1(2 * (x * y) + 2 * (w * z)); // 2xy - 2wz
matrix.setX2(2 * (x * z) - 2 * (w * y)); //2xz + 2wy
matrix.setX3(0);
matrix.setY0(2 * (x * y) - 2 * (w * z)); //2xy + 2wz
matrix.setY1(1 - 2 * (x * x) - 2 * (z * z)); //1 - 2x2 - 2z2
matrix.setY2(2 * (y * z) + 2 * (w * x)); // 2yz + 2wx
matrix.setY3(0);
matrix.setZ0(2 * (x * z) + 2 * (w * y)); //2xz + 2wy
matrix.setZ1(2 * (y * z) - 2 * (w * x)); //2yz - 2wx
matrix.setZ2(1 - 2 * (x * x) - 2 * (y * y)); //1 - 2x2 - 2y2
matrix.setZ3(0);
matrix.setW0(0);
matrix.setW1(0);
matrix.setW2(0);
matrix.setW3(1);
}
/**
* Get an axis angle representation of this quaternion.
*
* @param output Vector4f axis angle.
*/
public void toAxisAngle(Vector4f output) {
if (getW() > 1) {
normalise(); // if w>1 acos and sqrt will produce errors, this cant happen if quaternion is normalised
}
float angle = 2 * (float) Math.toDegrees(Math.acos(getW()));
float x;
float y;
float z;
float s = (float) Math.sqrt(1 - getW() * getW()); // assuming quaternion normalised then w is less than 1, so term always positive.
if (s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt
// if s close to zero then direction of axis not important
x = points[0]; // if it is important that axis is normalised then replace with x=1; y=z=0;
y = points[1];
z = points[2];
} else {
x = points[0] / s; // normalise axis
y = points[1] / s;
z = points[2] / s;
}
output.points[0] = x;
output.points[1] = y;
output.points[2] = z;
output.points[3] = angle;
}
/**
* Returns the heading, attitude and bank of this quaternion as euler angles in the double array respectively
*
* @return An array of size 3 containing the euler angles for this quaternion
*/
public double[] toEulerAngles() {
double[] ret = new double[3];
ret[0] = Math.atan2(2 * points[1] * getW() - 2 * points[0] * points[2], 1 - 2 * (points[1] * points[1]) - 2
* (points[2] * points[2])); // atan2(2*qy*qw-2*qx*qz , 1 - 2*qy2 - 2*qz2)
ret[1] = Math.asin(2 * points[0] * points[1] + 2 * points[2] * getW()); // asin(2*qx*qy + 2*qz*qw)
ret[2] = Math.atan2(2 * points[0] * getW() - 2 * points[1] * points[2], 1 - 2 * (points[0] * points[0]) - 2
* (points[2] * points[2])); // atan2(2*qx*qw-2*qy*qz , 1 - 2*qx2 - 2*qz2)
return ret;
}
/**
* Sets the quaternion to an identity quaternion of 0,0,0,1.
*/
public void loadIdentityQuat() {
this.dirty = true;
setX(0);
setY(0);
setZ(0);
setW(1);
}
@Override
public String toString() {
return "{X: " + getX() + ", Y:" + getY() + ", Z:" + getZ() + ", W:" + getW() + "}";
}
/**
* This is an internal method used to build a quaternion from a rotation matrix and then sets the current quaternion
* from that matrix.
*
*/
private void generateQuaternionFromMatrix() {
float qx;
float qy;
float qz;
float qw;
float[] mat = matrix.getMatrix();
int[] indices = null;
if (this.matrix.size() == 16) {
if (this.matrix.isColumnMajor()) {
indices = Matrixf4x4.matIndCol16_3x3;
} else {
indices = Matrixf4x4.matIndRow16_3x3;
}
} else {
if (this.matrix.isColumnMajor()) {
indices = Matrixf4x4.matIndCol9_3x3;
} else {
indices = Matrixf4x4.matIndRow9_3x3;
}
}
int m00 = indices[0];
int m01 = indices[1];
int m02 = indices[2];
int m10 = indices[3];
int m11 = indices[4];
int m12 = indices[5];
int m20 = indices[6];
int m21 = indices[7];
int m22 = indices[8];
float tr = mat[m00] + mat[m11] + mat[m22];
if (tr > 0) {
float s = (float) Math.sqrt(tr + 1.0) * 2; // S=4*qw
qw = 0.25f * s;
qx = (mat[m21] - mat[m12]) / s;
qy = (mat[m02] - mat[m20]) / s;
qz = (mat[m10] - mat[m01]) / s;
} else if ((mat[m00] > mat[m11]) & (mat[m00] > mat[m22])) {
float s = (float) Math.sqrt(1.0 + mat[m00] - mat[m11] - mat[m22]) * 2; // S=4*qx
qw = (mat[m21] - mat[m12]) / s;
qx = 0.25f * s;
qy = (mat[m01] + mat[m10]) / s;
qz = (mat[m02] + mat[m20]) / s;
} else if (mat[m11] > mat[m22]) {
float s = (float) Math.sqrt(1.0 + mat[m11] - mat[m00] - mat[m22]) * 2; // S=4*qy
qw = (mat[m02] - mat[m20]) / s;
qx = (mat[m01] + mat[m10]) / s;
qy = 0.25f * s;
qz = (mat[m12] + mat[m21]) / s;
} else {
float s = (float) Math.sqrt(1.0 + mat[m22] - mat[m00] - mat[m11]) * 2; // S=4*qz
qw = (mat[m10] - mat[m01]) / s;
qx = (mat[m02] + mat[m20]) / s;
qy = (mat[m12] + mat[m21]) / s;
qz = 0.25f * s;
}
setX(qx);
setY(qy);
setZ(qz);
setW(qw);
}
/**
* You can set the values for this quaternion based off a rotation matrix. If the matrix you supply is not a
* rotation matrix this will fail. You MUST provide a 4x4 matrix.
*
* @param matrix A column major rotation matrix
*/
public void setColumnMajor(float[] matrix) {
this.matrix.setMatrix(matrix);
this.matrix.setColumnMajor(true);
generateQuaternionFromMatrix();
}
/**
* You can set the values for this quaternion based off a rotation matrix. If the matrix you supply is not a
* rotation matrix this will fail.
*
* @param matrix A column major rotation matrix
*/
public void setRowMajor(float[] matrix) {
this.matrix.setMatrix(matrix);
this.matrix.setColumnMajor(false);
generateQuaternionFromMatrix();
}
/**
* Set this quaternion from axis angle values. All rotations are in degrees.
*
* @param azimuth The rotation around the z axis
* @param pitch The rotation around the y axis
* @param roll The rotation around the x axis
*/
public void setEulerAngle(float azimuth, float pitch, float roll) {
double heading = Math.toRadians(roll);
double attitude = Math.toRadians(pitch);
double bank = Math.toRadians(azimuth);
double c1 = Math.cos(heading / 2);
double s1 = Math.sin(heading / 2);
double c2 = Math.cos(attitude / 2);
double s2 = Math.sin(attitude / 2);
double c3 = Math.cos(bank / 2);
double s3 = Math.sin(bank / 2);
double c1c2 = c1 * c2;
double s1s2 = s1 * s2;
setW((float) (c1c2 * c3 - s1s2 * s3));
setX((float) (c1c2 * s3 + s1s2 * c3));
setY((float) (s1 * c2 * c3 + c1 * s2 * s3));
setZ((float) (c1 * s2 * c3 - s1 * c2 * s3));
dirty = true;
}
/**
* Rotation is in degrees. Set this quaternion from the supplied axis angle.
*
* @param vec The vector of rotation
* @param rot The angle of rotation around that vector in degrees.
*/
public void setAxisAngle(Vector3f vec, float rot) {
double s = Math.sin(Math.toRadians(rot / 2));
setX(vec.getX() * (float) s);
setY(vec.getY() * (float) s);
setZ(vec.getZ() * (float) s);
setW((float) Math.cos(Math.toRadians(rot / 2)));
dirty = true;
}
public void setAxisAngleRad(Vector3f vec, double rot) {
double s = rot / 2;
setX(vec.getX() * (float) s);
setY(vec.getY() * (float) s);
setZ(vec.getZ() * (float) s);
setW((float) rot / 2);
dirty = true;
}
/**
* @return Returns this Quaternion in the Rotation Matrix representation
*/
public Matrixf4x4 getMatrix4x4() {
//toMatrixColMajor();
if (dirty) {
convertQuatToMatrix();
dirty = false;
}
return this.matrix;
}
public void copyFromVec3(Vector3f vec, float w) {
copyFromV3f(vec, w);
}
/**
* Get a linear interpolation between this quaternion and the input quaternion, storing the result in the output
* quaternion.
*
* @param input The quaternion to be slerped with this quaternion.
* @param output The quaternion to store the result in.
* @param t The ratio between the two quaternions where 0 <= t <= 1.0 . Increase value of t will bring rotation
* closer to the input quaternion.
*/
public void slerp(Quaternion input, Quaternion output, float t) {
// Calculate angle between them.
//double cosHalftheta = this.dotProduct(input);
Quaternion bufferQuat = null;
float cosHalftheta = this.dotProduct(input);
if (cosHalftheta < 0) {
bufferQuat = new Quaternion();
cosHalftheta = -cosHalftheta;
bufferQuat.points[0] = (-input.points[0]);
bufferQuat.points[1] = (-input.points[1]);
bufferQuat.points[2] = (-input.points[2]);
bufferQuat.points[3] = (-input.points[3]);
} else {
bufferQuat = input;
}
/**
* if(dot < 0.95f){
* double angle = Math.acos(dot);
* double ratioA = Math.sin((1 - t) * angle);
* double ratioB = Math.sin(t * angle);
* double divisor = Math.sin(angle);
*
* //Calculate Quaternion
* output.setW((float)((this.getW() * ratioA + input.getW() * ratioB)/divisor));
* output.setX((float)((this.getX() * ratioA + input.getX() * ratioB)/divisor));
* output.setY((float)((this.getY() * ratioA + input.getY() * ratioB)/divisor));
* output.setZ((float)((this.getZ() * ratioA + input.getZ() * ratioB)/divisor));
* }
* else{
* lerp(input, output, t);
* }
*/
// if qa=qb or qa=-qb then theta = 0 and we can return qa
if (Math.abs(cosHalftheta) >= 1.0) {
output.points[0] = (this.points[0]);
output.points[1] = (this.points[1]);
output.points[2] = (this.points[2]);
output.points[3] = (this.points[3]);
} else {
double sinHalfTheta = Math.sqrt(1.0 - cosHalftheta * cosHalftheta);
// if theta = 180 degrees then result is not fully defined
// we could rotate around any axis normal to qa or qb
//if(Math.abs(sinHalfTheta) < 0.001){
//output.setW(this.getW() * 0.5f + input.getW() * 0.5f);
//output.setX(this.getX() * 0.5f + input.getX() * 0.5f);
//output.setY(this.getY() * 0.5f + input.getY() * 0.5f);
//output.setZ(this.getZ() * 0.5f + input.getZ() * 0.5f);
// lerp(bufferQuat, output, t);
//}
//else{
double halfTheta = Math.acos(cosHalftheta);
double ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta;
double ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
//Calculate Quaternion
output.points[3] = ((float) (points[3] * ratioA + bufferQuat.points[3] * ratioB));
output.points[0] = ((float) (this.points[0] * ratioA + bufferQuat.points[0] * ratioB));
output.points[1] = ((float) (this.points[1] * ratioA + bufferQuat.points[1] * ratioB));
output.points[2] = ((float) (this.points[2] * ratioA + bufferQuat.points[2] * ratioB));
//}
}
}
}

View File

@@ -0,0 +1,39 @@
package org.hitlabnz.sensor_fusion_demo.representation;
import java.io.Serializable;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Leigh Beattie
*
* At the moment this is a place holder for objects that can be put in the scene graph. There may be some
* requirements later specified.
*/
public class Renderable implements Serializable {
/**
* ID for serialisation
*/
private static final long serialVersionUID = 6701586807666461858L;
//Used in data managemenst and synchronisation. If you make a renderable then you should change this boolean to true.
protected boolean dirty = true;
protected ReentrantLock lock = new ReentrantLock();
public boolean dirty() {
return dirty;
}
public void setClean() {
this.dirty = false;
}
public void setDirty() {
this.dirty = true;
}
public ReentrantLock getLock() {
return this.lock;
}
}

View File

@@ -0,0 +1,295 @@
package org.hitlabnz.sensor_fusion_demo.representation;
/**
* 3-dimensional vector with conventient getters and setters. Additionally this class is serializable and
*/
public class Vector3f extends Renderable {
/**
* ID for serialisation
*/
private static final long serialVersionUID = -4565578579900616220L;
/**
* A float array was chosen instead of individual variables due to performance concerns. Converting the points into
* an array at run time can cause slowness so instead we use one array and extract the individual variables with get
* methods.
*/
protected float[] points = new float[3];
/**
* Initialises the vector with the given values
*
* @param x the x-component
* @param y the y-component
* @param z the z-component
*/
public Vector3f(float x, float y, float z) {
this.points[0] = x;
this.points[1] = y;
this.points[2] = z;
}
/**
* Initialises all components of this vector with the given same value.
*
* @param value Initialisation value for all components
*/
public Vector3f(float value) {
this.points[0] = value;
this.points[1] = value;
this.points[2] = value;
}
/**
* Instantiates a new vector3f.
*/
public Vector3f() {
}
/**
* Copy constructor
*/
public Vector3f(Vector3f vector) {
this.points[0] = vector.points[0];
this.points[1] = vector.points[1];
this.points[2] = vector.points[2];
}
/**
* Initialises this vector from a 4-dimensional vector. If the fourth component is not zero, a normalisation of all
* components will be performed.
*
* @param vector The 4-dimensional vector that should be used for initialisation
*/
public Vector3f(Vector4f vector) {
if (vector.w() != 0) {
this.points[0] = vector.x() / vector.w();
this.points[1] = vector.y() / vector.w();
this.points[2] = vector.z() / vector.w();
} else {
this.points[0] = vector.x();
this.points[1] = vector.y();
this.points[2] = vector.z();
}
}
/**
* Returns this vector as float-array.
*
* @return the float[]
*/
public float[] toArray() {
return this.points;
}
/**
* Adds a vector to this vector
*
* @param summand the vector that should be added component-wise
*/
public void add(Vector3f summand) {
this.points[0] += summand.points[0];
this.points[1] += summand.points[1];
this.points[2] += summand.points[2];
}
/**
* Adds the value to all components of this vector
*
* @param summand The value that should be added to all components
*/
public void add(float summand) {
this.points[0] += summand;
this.points[1] += summand;
this.points[2] += summand;
}
/**
*
* @param subtrahend
*/
public void subtract(Vector3f subtrahend) {
this.points[0] -= subtrahend.points[0];
this.points[1] -= subtrahend.points[1];
this.points[2] -= subtrahend.points[2];
}
/**
* Multiply by scalar.
*
* @param scalar the scalar
*/
public void multiplyByScalar(float scalar) {
this.points[0] *= scalar;
this.points[1] *= scalar;
this.points[2] *= scalar;
}
/**
* Normalize.
*/
public void normalize() {
double a = Math.sqrt(points[0] * points[0] + points[1] * points[1] + points[2] * points[2]);
this.points[0] = (float) (this.points[0] / a);
this.points[1] = (float) (this.points[1] / a);
this.points[2] = (float) (this.points[2] / a);
}
/**
* Gets the x.
*
* @return the x
*/
public float getX() {
return points[0];
}
/**
* Gets the y.
*
* @return the y
*/
public float getY() {
return points[1];
}
/**
* Gets the z.
*
* @return the z
*/
public float getZ() {
return points[2];
}
/**
* Sets the x.
*
* @param x the new x
*/
public void setX(float x) {
this.points[0] = x;
}
/**
* Sets the y.
*
* @param y the new y
*/
public void setY(float y) {
this.points[1] = y;
}
/**
* Sets the z.
*
* @param z the new z
*/
public void setZ(float z) {
this.points[2] = z;
}
/**
* Functions for convenience
*/
public float x() {
return this.points[0];
}
public float y() {
return this.points[1];
}
public float z() {
return this.points[2];
}
public void x(float x) {
this.points[0] = x;
}
public void y(float y) {
this.points[1] = y;
}
public void z(float z) {
this.points[2] = z;
}
public void setXYZ(float x, float y, float z) {
this.points[0] = x;
this.points[1] = y;
this.points[2] = z;
}
/**
* Return the dot product of this vector with the input vector
*
* @param inputVec The vector you want to do the dot product with against this vector.
* @return Float value representing the scalar of the dot product operation
*/
public float dotProduct(Vector3f inputVec) {
return points[0] * inputVec.points[0] + points[1] * inputVec.points[1] + points[2] * inputVec.points[2];
}
/**
* Get the cross product of this vector and another vector. The result will be stored in the output vector.
*
* @param inputVec The vector you want to get the dot product of against this vector.
* @param outputVec The vector to store the result in.
*/
public void crossProduct(Vector3f inputVec, Vector3f outputVec) {
outputVec.setX(points[1] * inputVec.points[2] - points[2] * inputVec.points[1]);
outputVec.setY(points[2] * inputVec.points[0] - points[0] * inputVec.points[2]);
outputVec.setZ(points[0] * inputVec.points[1] - points[1] * inputVec.points[0]);
}
public Vector3f crossProduct(Vector3f in) {
Vector3f out = new Vector3f();
crossProduct(in, out);
return out;
}
/**
* If you need to get the length of a vector then use this function.
*
* @return The length of the vector
*/
public float getLength() {
return (float) Math.sqrt(points[0] * points[0] + points[1] * points[1] + points[2] * points[2]);
}
@Override
public String toString() {
return "X:" + points[0] + " Y:" + points[1] + " Z:" + points[2];
}
/**
* Clone the input vector so that this vector has the same values.
*
* @param source The vector you want to clone.
*/
public void clone(Vector3f source) {
// this.points[0] = source.points[0];
// this.points[1] = source.points[1];
// this.points[2] = source.points[2];
System.arraycopy(source.points, 0, points, 0, 3);
}
/**
* Clone the input vector so that this vector has the same values.
*
* @param source The vector you want to clone.
*/
public void clone(float[] source) {
// this.points[0] = source[0];
// this.points[1] = source[1];
// this.points[2] = source[2];
System.arraycopy(source, 0, points, 0, 3);
}
}

View File

@@ -0,0 +1,296 @@
package org.hitlabnz.sensor_fusion_demo.representation;
import java.io.Serializable;
/**
* Representation of a four-dimensional float-vector
*/
public class Vector4f extends Renderable implements Serializable {
/**
* ID for Serialisation
*/
private static final long serialVersionUID = 1L;
/** The points. */
protected float points[] = { 0, 0, 0, 0 };
/**
* Instantiates a new vector4f.
*
* @param x the x
* @param y the y
* @param z the z
* @param w the w
*/
public Vector4f(float x, float y, float z, float w) {
this.points[0] = x;
this.points[1] = y;
this.points[2] = z;
this.points[3] = w;
}
/**
* Instantiates a new vector4f.
*/
public Vector4f() {
this.points[0] = 0;
this.points[1] = 0;
this.points[2] = 0;
this.points[3] = 0;
}
public Vector4f(Vector3f vector3f, float w) {
this.points[0] = vector3f.x();
this.points[1] = vector3f.y();
this.points[2] = vector3f.z();
this.points[3] = w;
}
/**
* To array.
*
* @return the float[]
*/
public float[] ToArray() {
return points;
}
public void copyVec4(Vector4f vec) {
this.points[0] = vec.points[0];
this.points[1] = vec.points[1];
this.points[2] = vec.points[2];
this.points[3] = vec.points[3];
}
/**
* Adds the.
*
* @param vector the vector
*/
public void add(Vector4f vector) {
this.points[0] += vector.points[0];
this.points[1] += vector.points[1];
this.points[2] += vector.points[2];
this.points[3] += vector.points[3];
}
public void add(Vector3f vector, float w) {
this.points[0] += vector.x();
this.points[1] += vector.y();
this.points[2] += vector.z();
this.points[3] += w;
}
public void subtract(Vector4f vector) {
this.points[0] -= vector.points[0];
this.points[1] -= vector.points[1];
this.points[2] -= vector.points[2];
this.points[3] -= vector.points[3];
}
public void subtract(Vector4f vector, Vector4f output) {
output.setXYZW(this.points[0] - vector.points[0], this.points[1] - vector.points[1], this.points[2]
- vector.points[2], this.points[3] - vector.points[3]);
}
public void subdivide(Vector4f vector) {
this.points[0] /= vector.points[0];
this.points[1] /= vector.points[1];
this.points[2] /= vector.points[2];
this.points[3] /= vector.points[3];
}
/**
* Multiply by scalar.
*
* @param scalar the scalar
*/
public void multiplyByScalar(float scalar) {
this.points[0] *= scalar;
this.points[1] *= scalar;
this.points[2] *= scalar;
this.points[3] *= scalar;
}
public float dotProduct(Vector4f input) {
return this.points[0] * input.points[0] + this.points[1] * input.points[1] + this.points[2] * input.points[2]
+ this.points[3] * input.points[3];
}
/**
* Linear interpolation between two vectors storing the result in the output variable.
*
* @param input
* @param output
* @param t
*/
public void lerp(Vector4f input, Vector4f output, float t) {
output.points[0] = (points[0] * (1.0f * t) + input.points[0] * t);
output.points[1] = (points[1] * (1.0f * t) + input.points[1] * t);
output.points[2] = (points[2] * (1.0f * t) + input.points[2] * t);
output.points[3] = (points[3] * (1.0f * t) + input.points[3] * t);
}
/**
* Normalize.
*/
public void normalize() {
if (points[3] == 0)
return;
points[0] /= points[3];
points[1] /= points[3];
points[2] /= points[3];
double a = Math.sqrt(this.points[0] * this.points[0] + this.points[1] * this.points[1] + this.points[2]
* this.points[2]);
points[0] = (float) (this.points[0] / a);
points[1] = (float) (this.points[1] / a);
points[2] = (float) (this.points[2] / a);
}
/**
* Gets the x.
*
* @return the x
*/
public float getX() {
return this.points[0];
}
/**
* Gets the y.
*
* @return the y
*/
public float getY() {
return this.points[1];
}
/**
* Gets the z.
*
* @return the z
*/
public float getZ() {
return this.points[2];
}
/**
* Gets the w.
*
* @return the w
*/
public float getW() {
return this.points[3];
}
/**
* Sets the x.
*
* @param x the new x
*/
public void setX(float x) {
this.points[0] = x;
}
/**
* Sets the y.
*
* @param y the new y
*/
public void setY(float y) {
this.points[1] = y;
}
/**
* Sets the z.
*
* @param z the new z
*/
public void setZ(float z) {
this.points[2] = z;
}
/**
* Sets the w.
*
* @param w the new w
*/
public void setW(float w) {
this.points[3] = w;
}
public float x() {
return this.points[0];
}
public float y() {
return this.points[1];
}
public float z() {
return this.points[2];
}
public float w() {
return this.points[3];
}
public void x(float x) {
this.points[0] = x;
}
public void y(float y) {
this.points[1] = y;
}
public void z(float z) {
this.points[2] = z;
}
public void w(float w) {
this.points[3] = w;
}
public void setXYZW(float x, float y, float z, float w) {
this.points[0] = x;
this.points[1] = y;
this.points[2] = z;
this.points[3] = w;
}
/**
* Compare this vector4f to the supplied one
*
* @param rhs True if they match, false other wise.
* @return
*/
public boolean compareTo(Vector4f rhs) {
boolean ret = false;
if (this.points[0] == rhs.points[0] && this.points[1] == rhs.points[1] && this.points[2] == rhs.points[2]
&& this.points[3] == rhs.points[3])
ret = true;
return ret;
}
/**
* Copies the data from the supplied vec3 into this vec4 plus the supplied w.
*
* @param input The x y z values to copy in.
* @param w The extra w element to copy in
*/
public void copyFromV3f(Vector3f input, float w) {
points[0] = (input.x());
points[1] = (input.y());
points[2] = (input.z());
points[3] = (w);
}
@Override
public String toString() {
return "X:" + points[0] + " Y:" + points[1] + " Z:" + points[2] + " W:" + points[3];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,14 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AboutActivity" >
<WebView
android:id="@+id/webViewAbout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
</RelativeLayout>

View File

@@ -0,0 +1,23 @@
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SensorSelectionActivity" >
<!--
This title strip will display the currently visible page title, as well as the page
titles for adjacent pages.
-->
<android.support.v4.view.PagerTitleStrip
android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:textColor="#fff" />
</android.support.v4.view.ViewPager>

View File

@@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_about"
android:orderInCategory="100"
android:showAsAction="always"
android:title="@string/action_about"/>
</menu>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Sensorfusions Demo</string>
<string name="action_about">Über</string>
<string name="title_main_activity">Sensorfusions Demo</string>
<string name="title_section1">Improved Orientation Sensor 1</string>
<string name="title_section2">Improved Orientation Sensor 2</string>
<string name="title_section3">Android Rotation Vector</string>
<string name="title_section4">Kalibriertes Gyroskop</string>
<string name="title_section5">Gravitation und Kompass</string>
<string name="title_section6">Akzelerometer und Kompass</string>
<string name="title_activity_sensor_selection">Sensorfusions Demo</string>
<string name="title_activity_about">Über</string>
<string name="gyroscope_missing">Fehlendes Gyroskop</string>
<string name="gyroscope_missing_message">Dieses Gerät hat keinen Gyroskop-Sensor der aber notwendig ist, damit diese App richtig funktioniert. Bitte verwende ein aktuelleres Gerät, das ein Gyroskop hat.</string>
<string name="OK">OK</string>
</resources>

View File

@@ -0,0 +1,8 @@
<resources>
<!--
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw600dp devices (e.g. 7" tablets) here.
-->
</resources>

View File

@@ -0,0 +1,9 @@
<resources>
<!--
Customize dimensions originally defined in res/values/dimens.xml (such as
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
-->
<dimen name="activity_horizontal_margin">128dp</dimen>
</resources>

View File

@@ -0,0 +1,11 @@
<resources>
<!--
Base application theme for API 11+. This theme completely replaces
AppBaseTheme from res/values/styles.xml on API 11+ devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
<!-- API 11 theme customizations can go here. -->
</style>
</resources>

View File

@@ -0,0 +1,12 @@
<resources>
<!--
Base application theme for API 14+. This theme completely replaces
AppBaseTheme from BOTH res/values/styles.xml and
res/values-v11/styles.xml on API 14+ devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- API 14 theme customizations can go here. -->
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Sensor fusion demo</string>
<string name="action_about">About</string>
<string name="title_main_activity">Sensor fusion demo</string>
<string name="title_section1">Improved Orientation Sensor 1</string>
<string name="title_section2">Improved Orientation Sensor 2</string>
<string name="title_section3">Android Rotation Vector</string>
<string name="title_section4">Calibrated Gyroscope</string>
<string name="title_section5">Gravity and Compass</string>
<string name="title_section6">Accelerometer and Compass</string>
<string name="title_activity_sensor_selection">Sensor fusion demo</string>
<string name="title_activity_about">About</string>
<string name="gyroscope_missing">Gyroscope Missing</string>
<string name="gyroscope_missing_message">Your device has no hardware gyroscope sensor, which would be necessary for this app to work properly. Please run it on a newer device that has a gyroscope.</string>
<string name="OK">OK</string>
</resources>

View File

@@ -0,0 +1,20 @@
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
</resources>