Search code examples
androidandroid-layoutaugmented-realityaxesdigital-compass

Android AR Compass Display


I am working on an Augmented Reality (AR) app for an Android phone. I would like to display my orientation data in the form of two rotating axes: one at the top of the screen to show the current azimuth (North, East, South, West), and one on the side to show the altitude/elevation. I'm new to Android programming, so I'm not really sure how to go about creating these axes visually.

My first thought was that I could use predefined images that somehow wrap around upon hitting the edges of the screen (as if the image was on the outside of a rotating cylinder). From here, it would be a simple matter of lining up the image with the information I'm pulling from the sensors.

Is there some way to wrap images so that they can be rotated in a virtual plane in this manner? Can anybody suggest a better solution to my original problem?


Solution

  • I'm pressed for time, so I've come up with a hard-coded solution that could probably be extended to a more general case without too much trouble. To create the axes, I'm programmatically generating a bunch of Path objects and then drawing them to my canvas. Then I simply translate the canvas back and forth based on the azimuth values I'm collecting.

    Here is a copy of the class I use to generate the azimuth axis:

    class AzimuthOverlay extends View {
    
    // Values hardcoded for fullscreen landscape
    // Width = 800 pixels
    
    int tickPixels = 40; // Number of pixels between tick marks
    final int MAJORTICKS = 36;
    final int MINORTICKS = 36;
    
    // Need additional 10 for proper wrapping
    final int TOTALOBJECTS = MAJORTICKS + MINORTICKS + 10;
    
    public double mAzimuth; // Set externally by a SensorEventListener class
    
    private Paint ticPaint = new Paint();
    private Paint numPaint = new Paint();
    private ArrayList<Path> axis = new ArrayList<Path>(TOTALOBJECTS);
    
    public AzimuthOverlay(Context context) {
        super(context);
    
        ticPaint.setAntiAlias(true);
        ticPaint.setColor(Color.GREEN);
        ticPaint.setStyle(Paint.Style.FILL);
    
        numPaint.setColor(Color.GREEN);
        numPaint.setStyle(Paint.Style.STROKE);
        numPaint.setStrokeWidth(2);
    
        // Extend to the left of the screen
        for (int i = -10; i < 0; i++) {
            if (i % 2 == 0) {
                axis.add(getMajorTick(i * tickPixels));
            }
            else {
                axis.add(getNumber(360 + 5*i, i * tickPixels));
            }
        }
    
        // Create axis (numbers on minor ticks)
        for (int i = 0; i < TOTALOBJECTS; i++) {
            if (i % 2 == 0) {
                axis.add(getMajorTick(i * tickPixels));
            }
            else {
                axis.add(getNumber((5*i)%360, i * tickPixels));
            }
        }
    
    
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
    
        canvas.translate((int)-mAzimuth * 8, 0); // 8 pixels per degree
    
        boolean toggle = true;  // Alternate between ticks and numbers
        for (Path p : axis) {
            if (toggle) {
                canvas.drawPath(p, ticPaint);
            }
            else {
                canvas.drawPath(p, numPaint);
            }
            toggle = (toggle == false) ? true : false;
        }
    
        super.onDraw(canvas);
    }
    
    // Create big tick marks as Path object
    private Path getMajorTick(int offset) {
        Path mypath = new Path();
    
        mypath.moveTo(-2 + offset, 0);
        mypath.lineTo(-2 + offset, 20);
        mypath.lineTo(2 + offset, 20);
        mypath.lineTo(2 + offset, 0);
        mypath.close();
    
        return mypath;
    }
    
    // Create small tick marks as Path object
    private Path getMinorTick(int offset) {
        Path mypath = new Path();
    
        mypath.moveTo(-2 + offset, 0);
        mypath.lineTo(-2 + offset, 10);
        mypath.lineTo(2 + offset, 10);
        mypath.lineTo(2 + offset, 0);
        mypath.close();
    
        return mypath;
    }
    
    // Create individual digits as Path object
    private Path getDigit(int digit, int offset) {
        Path mypath = new Path();
    
        final int lx = -6;
        final int mx = 0;
        final int rx = 6;
        final int ty = 0;
        final int my = 6;
        final int by = 12;
    
        final int doffset = 2;
    
        switch(digit) {
        case 0:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            mypath.close();
            break;
        case 1:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(mx + offset, ty + doffset);
            mypath.lineTo(mx + offset, by + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            break;
        case 2:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            mypath.lineTo(lx + offset, my + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            break;
        case 3:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            mypath.moveTo(lx + offset, my + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            break;
        case 4:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(lx + offset, my + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            break;
        case 5:
            mypath.moveTo(rx + offset, ty + doffset);
            mypath.lineTo(lx + offset, ty + doffset);
            mypath.lineTo(lx + offset, my + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            break;
        case 6:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            mypath.lineTo(lx + offset, my + doffset);
            break;
        case 7:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            break;
        case 8:
            mypath.moveTo(lx + offset, ty + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(rx + offset, by + doffset);
            mypath.lineTo(lx + offset, by + doffset);
            mypath.lineTo(lx + offset, ty + doffset);
            mypath.moveTo(lx + offset, my + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            break;
        case 9:
            mypath.moveTo(rx + offset, by + doffset);
            mypath.lineTo(rx + offset, ty + doffset);
            mypath.lineTo(lx + offset, ty + doffset);
            mypath.lineTo(lx + offset, my + doffset);
            mypath.lineTo(rx + offset, my + doffset);
            break;
        }
    
        return mypath;
    }
    
    // Create a number up to 3 digits as a Path object
    private Path getNumber(int number, int offset) {
        Path mypath = new Path();
    
        final int digitoffset = 7;
    
        int digit;
        int temp;
        if (number > 99) { // 3-digit number
            digit = number / 100;
            mypath = getDigit(digit, offset - 2*digitoffset);
            temp = (number % 100);
            digit = temp / 10;
            mypath.addPath(getDigit(digit, offset));
            digit = temp % 10;
            mypath.addPath(getDigit(digit, offset + 2*digitoffset));
        }
        else if (number > 9) { // 2-digit number
            digit = number / 10;
            mypath = getDigit(digit, offset-digitoffset);
            mypath.addPath(getDigit(number % 10, offset+digitoffset));
        }
        else { // 1-digit number
            mypath = getDigit(number, offset);
        }
    
        return mypath;
    }
    
    protected void setAzimuth(double azimuth) {
        mAzimuth = azimuth;
    }   
    

    }