Project page

ShowMeEarth, another 3D app for Android




status : Work in progress

I've started this app a few day ago as an attempt to mix a 3D earth representation with real world data.

A user would browser a 3D earth on his tablet and I would display information that interest him : news, tweets...so they would be able to see information at the exact place it happens. At least that's the original concept.

Technically :

This project is still based on my sd3d https://github.com/kobr4/sd3d-android/tree/master/sd3d framework as I think that every android 3d project is an opportunity to improve my lib.

This time I will add bump mapping support, as I want my earth to be realistic.

I've followed Fabien Sanglard method and shaders :
http://fabiensanglard.net/bumpMapping/index.php

The result is not satisfactory, I will have to come back later to tweak it properly.

Font redering in OpenGL ES


Drawing text in 3D apps is still an open subject as there is no in-built feature to do so in OpenGL ES API.

1. The first technique used in SD3D is simply to load a bitmap font as a texture, then for each character draw a quad that contains the graphical text representation as a texture.
Advantage:
* Very fast as drawing quads in OpenGL is as fast as it can be
* Not tight to any particular OS function, will work in the same manner every platform
Drawbacks:
* Very limited : size / font style

2. The second technique I just implemented, is using an Android Bitmap Canvas and to draw text using Android API, then pass the Canvas content as a texture to
OpenGL ES API, then draw the texture as quad in front of the camera covering the screen surface using alpha blending to handle transparency.
Advantage:
* Very flexible: text size / font
Drawbacks:
* Slow : drawing an alpha blended quad in front of the screen is fillrate heavy.
* OS dependent, as it uses Android API.

Here is the code for the second method.
public class Sd3dBitmapQuadFont {
    Bitmap backingBitmap;
    Canvas backingCanvas;
    Paint backingPaint;
    int backingPixels[];
 
    public void init(int width, int height) {
        backingBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        backingCanvas = new Canvas(backingBitmap);
        backingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        backingPixels = new int[width*height];
    }
 
    public void drawText(String text, int x, int y) {
        backingPaint.setColor(Color.WHITE); // Text Color
        backingPaint.setStrokeWidth(70); // Text Size
        backingPaint.setTextSize(20);
        backingPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
        backingCanvas.drawText(text, x, y, backingPaint);
    }
 
    public void frameEnd() {
        Paint paint = new Paint();
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        backingCanvas.drawPaint(paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    }
 
    int count = 0;
    public int[] getPixels() {    
        backingBitmap.getPixels(backingPixels, 0, backingBitmap.getWidth(),
                0, 0, backingBitmap.getWidth(), backingBitmap.getHeight());
        return backingPixels;
    }
}


 
Sd3dBitmapQuadFont bmp = new Sd3dBitmapQuadFont();
bmp.init(screenWidth, screenHeight);
 


We draw text and create our OpenGL es texture on each frame using the following:
 
IntBuffer pixelBuffer = IntBuffer.wrap(bmp.getPixels());
IntBuffer buffer = IntBuffer.allocate(1);    
GLES20.glGenTextures(1, buffer);
GLES20.glBindTexture(GL11.GL_TEXTURE_2D, buffer.get(0));
GLES20.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_FALSE);
GLES20.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,this.screenWidth, this.screenHeight, 0,
    GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixelBuffer);
GLES20.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLES20.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GLES20.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,GL11.GL_CLAMP_TO_EDGE);        
 
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GL11.GL_TEXTURE_2D, buffer.get(0));
 


Camera motion


I want to achieve a camera motion that would rotate around earth like a satellite in geostationary orbit would. The lens would always be pointing at the center of mass.
This schema sums it up:


Once again, I will use the spherical coordinates system, at any moment, the cartesian coordinates of the the camera is known using this formula:
$x = r * \sin(\theta) * \cos(\phi)$
$y = r * \cos(\theta)$
$z = r * \sin(\theta) * \sin(\phi)$
r being distance to earth center.

Let say that we would maintain in our camera 2 variables Ax($\theta$) which stand for rotation around the X axis and Ay($\phi$) which is the rotation around the Y axis. Sd3d uses the left-hand coordinates system.

Intuitively, one can notice that when $\theta$ increased, the camera should rotate by the same amount but in the inverse direction to keep pointing at earth center.

Note that things are still simple because earth is at the origin of our referential P=(0,0,0).

One important thing in Android API : android.opengl.Matrix.setRotateEulerM gives a false result, it's not a correct rotation matrix as it is not homomorphic.
Unit testing at Mountain View, anyone ?
Use this function instead:
 
    public static void setRotateEulerM(float[] rm, int rmOffset, float x,
            float y, float z) {
        x = x * 0.01745329f;
        y = y * 0.01745329f;
        z = z * 0.01745329f;
        float sx = (float) Math.sin(x);
        float sy = (float) Math.sin(y);
        float sz = (float) Math.sin(z);
        float cx = (float) Math.cos(x);
        float cy = (float) Math.cos(y);
        float cz = (float) Math.cos(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] = sxsy * cz + cx * sz;
        rm[rmOffset + 5] = -sxsy * sz + cx * cz;
        rm[rmOffset + 6] = -sx * cy;
        rm[rmOffset + 7] = 0.0f;
 
        rm[rmOffset + 8] = -cxsy * cz + sx * sz;
        rm[rmOffset + 9] = cxsy * 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;
    }
 



It's starting to look pretty.

01/10/2013 : In order to add usefull information, I've decided to parse Yahoo YQL API to retrieve weather data.
Component is available here : https://github.com/kobr4/YqlRequester
To my suprise, it was possible to build and retrieve weather data for most of the 180 cities in my referential in one http request.

13/12/2014

It's been a year since I've last updated this app.
Nevermind, I've just managed to build it on brand new Android Dev Studio 1.0. That wasn't without hassle as the dedicated build tool : Gradle wasn't very cooperative.