Modern smartphones and tablets are usually equipped with a combo sensor which can measure gravity acceleration and the earths magnetic field. Gravity acceleration always points vertically downwards, the horizontal component of the magnetic field always north.
|
All in all, it works similar to an airplane
When the smartphone is inclined forth or back, the pitch changes. When moving sideways, the roll is changed.
Rotation around the own axis is considered as azimuth.
| ![]() |
// AndroidEx25.java package app.ex25; import ch.aplu.android.*; import android.graphics.Color; import static java.lang.Math.*; public class AndroidEx25 extends GameGrid { private final double r = 100; private GGStatusBar status; public AndroidEx25() { super(WHITE); status = addStatusBar(30); } public void main() { GGPanel p = getPanel(-110, 110, -110, 110); p.setAutoRefreshEnabled(false); GGComboSensor comboSensor = GGComboSensor.init(this); while (true) { p.clear(Color.rgb(70, 10, 10)); drawDisplay(p); float[] values = comboSensor.getOrientation(0); double azimuth = values[3]; double pitch = values[4]; double roll = values[5]; double b = pitch; double m = tan(toRadians(roll)); double x1 = (-b * m + sqrt((m * m + 1) * r * r - b * b)) / (m * m + 1); double y1 = m * x1 + b; double x2 = (-b * m - sqrt((m * m + 1) * r * r - b * b)) / (m * m + 1); double y2 = m * x2 + b; p.setPaintColor(GREEN); p.setLineWidth(2); p.line(x1, y1, x2, y2); p.setLineWidth(5); p.setPaintColor(YELLOW); double a = azimuth + 90; PointD p1 = new PointD(r / 2 * cos(toRadians(a)), r / 2 * sin(toRadians(a))); PointD p2 = new PointD((r / 2 + 20) * cos(toRadians(a)), (r / 2 + 20) * sin(toRadians(a))); p.line(p1, p2); p.setLineWidth(1); p.circle(p2, 5, true); p.setPaintColor(WHITE); status.setText(String.format("Az:% 4.1f Pitch:% 4.1f Roll:% 4.1f", azimuth, pitch, roll)); refresh(); delay(50); } } private void drawDisplay(GGPanel p) { PointD p1; PointD p2; p.circle(new PointD(0, 0), r, false); for (int a = 0; a < 360; a += 10) { p1 = new PointD((r - 10) * cos(toRadians(a)), (r - 10) * sin(toRadians(a))); p2 = new PointD(r * cos(toRadians(a)), r * sin(toRadians(a))); p.line(p1, p2); } for (int y = -5; y <= 5; y++) { p1 = new PointD(-20, 10 * y); p2 = new PointD(20, 10 * y); p.line(p1, p2); } p1 = new PointD(35, 3); p2 = new PointD(65, -3); p.rectangle(p1, p2, true); p1 = new PointD(-35, 3); p2 = new PointD(-65, -3); p.rectangle(p1, p2, true); p.circle(new PointD(0, 0), 5, true); } private void showValues(double pitch, double roll) { } } |
sensor = GGComboSensor.init() | Initializing the sensor. |
GGPanel p = getPanel(-110, 110, -110, 110) | For graphically displaying the values, a GGPanel is used. |
float[] values = comboSensor.getOrientation(0) | Saves the current sensor values (azimuth, pitch, roll) into an array. The parameter 0 gives the number of decimal places that should be returned for each value. |
values[3], values[4] , values[5] | values[0] = azimuth, values[1] = pitch, values[2] = roll. values[3], values[4], values[5] are azimuth, pitch and roll adjusted to the orientation of the application window when the application started. |
Example 2: Artificial horizon
|
![]() |
double pitch = 90 + value[4] | To make the pitch angle appear correct in vertical position, it is adjusted by 90 degrees. |
Example 3: Water level
|
![]() |
// AndroidEx26.java package app.ex26; import ch.aplu.android.*; import android.graphics.*; import android.hardware.SensorManager; public class AndroidEx26 extends GameGrid { private int size; private int radius; private Actor ball; private GGComboSensor sensor; private GGStatusBar status; public AndroidEx26() { super(windowZoom(600)); status = addStatusBar(30); } public void main() { size = getNbVertCells() / 2; radius = getNbVertCells() / 4; sensor = GGComboSensor.init(this, SensorManager.SENSOR_DELAY_FASTEST); ball = new Actor("marble"); addActor(ball, new Location(size, size)); setSimulationPeriod(30); drawBG(); doRun(); } public void act() { double[] a = sensor.getOrientation(3); status.setText(String.format("Pitch: % 6.1f deg Roll: % 6.1f deg", a[4], a[5])); Point p = new Point((int)(0.02 * a[5] * size),(int)(0.02 * a[4] * size)); p = reduce(p); ball.setLocation(new Location(size + p.x, size + p.y)); } private Point reduce(Point p) { // Reduce point to limiting circle if (p.x * p.x + p.y * p.y > radius * radius) return new Point( (int)(radius * p.x / Math.sqrt(p.x * p.x + p.y * p.y)), (int)(radius * p.y / Math.sqrt(p.x * p.x + p.y * p.y))); else return p; } private void drawBG() { GGBackground bg = getBg(); double zoomfactor = getZoomFactor(); bg.setPaintColor(YELLOW); int ballRadius = (int)(zoomfactor * 31); bg.fillCircle(new Point(size, size), radius + ballRadius); bg.setPaintColor(WHITE); bg.fillCircle(new Point(size, size), ballRadius); bg.setPaintColor(BLACK); bg.setLineWidth(4); bg.drawCircle(new Point(size, size), ballRadius); bg.drawCircle(new Point(size, size), 3 * ballRadius); } } |
sensor = GGComboSensor.init() | Initializing the sensors |
a[4] , a[5] | values[0] = azimuth, values[1] = pitch, values[2] = roll. values[3], values[4], values[5] are azimuth, pitch and roll adjusted to the orientation of the application window when the application started. |
reduce(p) | Sets the position of the sphere back to the peripherie of the yellow circle, even if it should move outside. |
int ballRadius = (int)(zoomfactor * 31) | Everything is adjusted to the size of the ball, that has 31 as radius. |
Example 4: Simulates the movement of a sphere on a flat surface.
Since the conversion to user coordinates into pixel coordinates is cumbersome and dependant on screen size and resolution, JDroidLib facilitates this process. In our example the the combo sensor measures the orientation of the device (pitch, roll) and computes through these the acceleration (gx, gy). This moves the sphere. The bigger the inclination, the faster the sphere moves. While rolling, the sphere draws its trace and stops when hitting the boarder of the red underlying circle.
|
// AndroidEx27.java package app.ex27; import ch.aplu.android.*; import android.graphics.*; public class AndroidEx27 extends GameGrid { protected final double boardSize = 10; // m protected double ballRadius; protected GGComboSensor sensor; protected GGPanel p; public AndroidEx27() { super(windowZoom(600)); } public void main() { // Coordinate system in user coordinates, origin in court center p = getPanel(-boardSize / 2, boardSize / 2, 0.5); p.setAutoRefreshEnabled(false); setSimulationPeriod(50); ballRadius = p.toUserDx(virtualToPixel(31)); // Sprite radius 31 px sensor = GGComboSensor.init(this); p.setPaintColor(Color.rgb(100, 20, 20)); p.circle(new PointD(0, 0), 0.9 * boardSize / 2, true); p.setPaintColor(Color.WHITE); Ball ball = new Ball(this); addActor(ball, new Location(getNbHorzCells() / 2, getNbVertCells() / 2)); doRun(); } } class Ball extends Actor { private AndroidEx27 app; // Physical variables (all in physical units) private double x, y; // Position (m) private double vx, vy; // Velocity (m/s) private double ax, ay; // Acceleration (m/s^2) private final double dt = 0.05; // Integration interval (s) private final double f = 0.2; // Friction (s^-1) public Ball(AndroidEx27 app) { super("marble"); this.app = app; // Physical initial conditions: x = 0; y = 0; vx = 0; vy = 0; } public void act() { double[] a = app.sensor.getAcceleration(0); double gx = -a[4]; double gy = a[3]; // New acceleration: ax = gx - f * vx; ay = gy - f * vy; // New velocity: double vxNew = vx + ax * dt; double vyNew = vy + ay * dt; // New position: double xNew = x + vxNew * dt; double yNew = y + vyNew * dt; // Test if in court if (!isInside(xNew, yNew)) // No->set speed to zero { vx = 0; vy = 0; return; // and quit } setLocation(new Location(app.p.toPixelX(xNew), app.p.toPixelY(yNew))); app.p.line(new PointD(x, y), new PointD(xNew, yNew)); vx = vxNew; vy = vyNew; x = xNew; y = yNew; } private boolean isInside(double x, double y) { double R = 0.9 * app.boardSize / 2 - app.ballRadius; return x * x + y * y <= R * R; } } |
Explanations to the program code:
double[] a = app.sensor.getAcceleration(0); double gx = -a[4]; double gy = a[3]; |
a[3], a[4] are the orientation sensor values adjusted to the orientation of the device when the application started. |
ax = gx - f * vx ay = gy - f * vy |
The measured acceleration subtracted friction proportional to the absolute of the velocity. |
vx = vx + ax * dt vy = vy + ay * dt |
Velocity respective to x and y. |
x = x + vx * dt
y = y + vy * dt |
Position respective to a x/y grid. |
if (!isInside(xNew, yNew)) { vx = 0; vy = 0; return; // and quit } |
Sets the velocity to 0 if the sphere would leave the red surface. |