Game apps for smartphones and tablets

Research project PHBern  
HomeStart online editorAndroid TurtlegraphicsPrintJava-Online

Orientation sensors


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.
With these values, the following angles can be computed:

Azimuth: The direction angle on a compass in degrees, positive towards the east.
Pitch: Forward inclination in degrees
Roll: Sidewise inclination in degrees
 

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.



Example 1
: Show sensor values
The current orientation (azimuth, pith and roll) are shown graphically and in the status bar. The azimuth is shown as yellow pointer, the two other values are indicated by the position and inclination of the green line. Is the device towards north, the azimuth takes the value 0. Pitch and roll are 0 when the device is lying on a flat surface. In example 2 however, the sensor values are shown similar to a artificial horizon, as it is employed in airplanes.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx25.zip)

 

// 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 = 100;
  private GGStatusBar status;

  public AndroidEx25()
  {
    super(WHITE);
    status = addStatusBar(30);
  }

  public void main()
  {
    GGPanel = 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 = pitch;
      double = tan(toRadians(roll));
      
      double x1 = (-b * + sqrt((m * + 1) * * - * b)) / (m * + 1);
      double y1 = * x1 + b;
      double x2 = (-b * - sqrt((m * + 1) * * - * b)) / (m * + 1);
      double y2 = * x2 + b;
      p.setPaintColor(GREEN);
      p.setLineWidth(2);
      p.line(x1, y1, x2, y2);
   
      p.setLineWidth(5);
      p.setPaintColor(YELLOW);
      double = azimuth + 90;
      PointD p1 = new PointD(/ * cos(toRadians(a)), r / 2 * sin(toRadians(a)));
      PointD p2 = new PointD((r / + 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 = 0; a < 360; a += 10)
    {
      p1 = new PointD((r - 10) * cos(toRadians(a)), (r - 10) * sin(toRadians(a)));
      p2 = new PointD(* cos(toRadians(a)), r * sin(toRadians(a)));
      p.line(p1, p2);
    }
    
    for (int = -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)
  {
   
  }
}

Erklärungen zum Programmcode:
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
Unlike example 1, the default position of the device is assumed to be vertical. This is similar to the instruments found in an airplane. According to the picture to the right, the airplaine would be in descent by 29° turning slightly to the right by 27°.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx25a.zip)

 

Explanations to the program code:
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
In this example, only pitch and roll are used. When the green sphere is in the middle of the display (inside the white circle), the device is perpendicular. Is the device inclined forwards, backwards or to the side, the sphere moves to the opposite direction, exactly as it would in a water level. The angles are printed in the status bar exact up to three decimal places. If these values take on 0, the device is perpendicular.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx26.zip)

 

// 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 = new Point((int)(0.02 * a[5] * size),(int)(0.02 * a[4] * size));
    p = reduce(p); 
    ball.setLocation(new Location(size + p.xsize + 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);
  }  
}

Explanations to the program code:
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.
When simulating physical movement, one best employs a user coordinate system with standard units (metres, metres per second). This makes it possible to use physical formulas without any additional constants.

Since the conversion to user coordinates into pixel coordinates is cumbersome and dependant on screen size and resolution, JDroidLib facilitates this process.
getPanel(ymin, ymax, xratio), toPixelY() respectively toUserX(), toUserY() from the class GGPanel can be used for this purpose.

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.

Beispiel im Online-Editor bearbeiten

App installieren auf Smartphone oder Tablet

QR-Code

Sources downloaden (AndroidEx27.zip)

 


// 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
    = 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 = 0.2; // Friction (s^-1)

  public Ball(AndroidEx27 app)
  {
    super("marble");
    this.app = app;

    // Physical initial conditions:
    = 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 - * vx;
    ay = gy - * vy;

    // New velocity:
    double vxNew = vx + ax * dt;
    double vyNew = vy + ay * dt;

    // New position:
    double xNew = + vxNew * dt;
    double yNew = + 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 = 0.9 * app.boardSize / - app.ballRadius;
    return * + * <= * 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.