Importing 3d Models in Android

Happy New Year! When making a 3d game, one of the processes to get streamlined is the 3d content creation pipeline. You could create 3d content by hard coding vertices and texture coordinates, but that is very time consuming. It’s great for simple things like the particles in the particle system demo or just a simple 1×1 tile, but for more complex 3d objects external tools need to be used.

I choose the tools Blender (www.blender.org) for 3d modeling, and Gimp (www.gimp.org/) for texture/image editting. The primary reason for choosing these tools is that they are free. Something like 3D Studio Max costs well over $3000 and Photoshop is around $900. This articles is NOT a tutorial on using Blender but will have some tips on issues that I had come across.

Which 3D Format?

This is a big question and the answer I came to was to use Ogre meshes. Ogre (www.ogre3d.org) is an open source 3d graphics engine used by some commercial games (one of which is Torchlight (www.torchlightgame.com). I was tinkering around with Python Ogre when looking at making a game so I was somewhat familiar with Ogre meshes. In particular I am using XML Ogre Meshes, as opposed to the binary .mesh file because I felt it would be easier to parse XML in Java rather than a bunch of bytes, and I don’t think Blender can export to the binary format. In a future article, I plan to tackle the popular MD2 format from Quake 2 which is a binary format. Depending on which tools you’re used to using or formats you have previously used in the past, it may be better for you to use another format.

Setting Up The Blender – Ogre Environment

Install Blender (at the time of writing, version 2.49b).

Install Ogre. You don’t have to install the whole Ogre, really all you need is the Ogre XML converter.

Install the Ogre export plugin for Blender found here, http://www.ogre3d.org/forums/viewtopic.php?t=45922. Follow the instructions on that page for installing the plugin.

Download and load my sample Blender project, ColorCube.zip. Unzip this file and load the ColorCube.blend file in Blender.

Creating an Ogre Mesh

You should see blender environment similar to this…

Blender Desktop

In the Ogre Meshes Exporter window, you may need to click on the preferences and define the path to where the OgreXMLConverter.exe is on your system. You may also specify where the mesh gets put when exported. The one setting of note in the main window is I have disabled “Fix Up Axis to Y”. In my application, I want the up axis to be the Z axis. To export this model, just click on the export button (as you can still see I have not renamed the object to something other than the default cube).

Exported

This will create a file called cube.mesh.xml, this file needs to be renamed to colorcube.xml (because xml files need to be all lowercase in Android). Already this is inefficent, having to rename the file every time slows down development. I haven’t found a way around this yet, it would be best if it wrote directly into the xml directory of my Android project and had a correct file name.

One thing I noticed when using Blender, as I was moving vertexes around Blender did not automatically convert the quads into triangles. This was done by selecting the whole object and hitting CTRL-T. The reason this was important is because if you haven’t defined where all your triangles are, when the export happens it will connect vertexes to make triangles as it sees fit and may not match what you’ve modeled.

Using an Ogre XML Mesh in Android

Now we have a mesh, let’s start coding. Start by creating a new project and in the resources add a directory called xml, and put the colorcube.xml file in it. Then I got the basic OpenGL activity found on the Android site http://android-developers.blogspot.com/2009/04/introducing-glsurfaceview.html. It’s a favorite starting point for me. I’ve modified it as such.

package com.bayninestudios.androidogremesh;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.os.Bundle;

public class AndroidOgreMesh extends Activity
{
    private GLSurfaceView mGLView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        mGLView = new ClearGLSurfaceView(this);
        setContentView(mGLView);
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        mGLView.onPause();
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        mGLView.onResume();
    }
}

class ClearGLSurfaceView extends GLSurfaceView
{
    ClearRenderer mRenderer;

    public ClearGLSurfaceView(Context context)
    {
        super(context);
        mRenderer = new ClearRenderer(context, this);
        setRenderer(mRenderer);
    }
}

class ClearRenderer implements GLSurfaceView.Renderer
{
    private ClearGLSurfaceView view;
    private DrawModel model;
    private float angleZ = 0f;

    public ClearRenderer(Context context, ClearGLSurfaceView view)
    {
        this.view = view;
        model = new DrawModel(context.getResources().getXml(R.xml.colorcube));
    }

    public void onSurfaceCreated(GL10 gl, EGLConfig config)
    {
        gl.glLoadIdentity();
        GLU.gluPerspective(gl, 25.0f, (view.getWidth() * 1f) / view.getHeight(), 1, 100);
        GLU.gluLookAt(gl, 0f, -10f, 6f, 0.0f, 0.0f, 0f, 0.0f, 1.0f, 1.0f);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
        gl.glEnable(GL10.GL_DEPTH_TEST);
    }

    public void onSurfaceChanged(GL10 gl, int w, int h)
    {
        gl.glViewport(0, 0, w, h);
    }

    public void onDrawFrame(GL10 gl)
    {
        gl.glClearColor(0f, 0f, 0f, 1.0f);
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glPushMatrix();
        gl.glRotatef(angleZ, 0f, 0f, 1f);
        model.draw(gl);
        gl.glPopMatrix();
        angleZ += 0.4f;
    }
}

This is standard OpenGL code the main part is the DrawModel class which we create and pass in the xml parser to it. The DrawModel class looks like this…

package com.bayninestudios.androidogremesh;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

import org.xmlpull.v1.XmlPullParserException;

import android.content.res.XmlResourceParser;
import android.util.Log;

public class DrawModel
{
    private FloatBuffer mVertexBuffer;
    private FloatBuffer mColorBuffer;
    private FloatBuffer mNormalBuffer;
    private ShortBuffer mIndexBuffer;
    private int faceCount = 0;

    public DrawModel(XmlResourceParser xrp)
    {
        float[] coords = null;
        float[] colcoords = null;
        short[] icoords = null;
        float[] ncoords = null;
        int vertexIndex = 0;
        int colorIndex = 0;
        int faceIndex = 0;
        int normalIndex = 0;
        try
        {
            while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT)
            {
                if (xrp.getEventType() == XmlResourceParser.START_TAG)
                {
                    String s = xrp.getName();
                    if (s.equals("faces"))
                    {
                        int i = xrp.getAttributeIntValue(null, "count", 0);
                        // now we know how many faces, so we know how large the
                        // triangle array should be
                        faceCount = i * 3; // three vertexes per face v1,v2,v3
                        icoords = new short[faceCount];
                    }
                    if (s.equals("geometry"))
                    {
                        int i = xrp.getAttributeIntValue(null, "vertexcount", 0);
                        // now we know how many vertexes, so we know how large
                        // the vertex, normal, texture and color arrays should be
                        // three vertex attributes per vertex x,y,z
                        coords = new float[i * 3];
                        // three normal attributes per vertex x,y,z
                        ncoords = new float[i * 3];
                        // four color attributes per vertex r,g,b,a
                        colcoords = new float[i * 4];
                    }
                    if (s.equals("position"))
                    {
                        float x = xrp.getAttributeFloatValue(null, "x", 0);
                        float y = xrp.getAttributeFloatValue(null, "y", 0);
                        float z = xrp.getAttributeFloatValue(null, "z", 0);
                        if (coords != null)
                        {
                            coords[vertexIndex++] = x;
                            coords[vertexIndex++] = y;
                            coords[vertexIndex++] = z;
                        }
                    }
                    if (s.equals("normal"))
                    {
                        float x = xrp.getAttributeFloatValue(null, "x", 0);
                        float y = xrp.getAttributeFloatValue(null, "y", 0);
                        float z = xrp.getAttributeFloatValue(null, "z", 0);
                        if (ncoords != null)
                        {
                            ncoords[normalIndex++] = x;
                            ncoords[normalIndex++] = y;
                            ncoords[normalIndex++] = z;
                        }
                    }
                    if (s.equals("face"))
                    {
                        short v1 = (short) xrp.getAttributeIntValue(null, "v1", 0);
                        short v2 = (short) xrp.getAttributeIntValue(null, "v2", 0);
                        short v3 = (short) xrp.getAttributeIntValue(null, "v3", 0);
                        if (icoords != null)
                        {
                            icoords[faceIndex++] = v1;
                            icoords[faceIndex++] = v2;
                            icoords[faceIndex++] = v3;
                        }
                    }
                    if (s.equals("colour_diffuse"))
                    {
                        String colorVal = (String) xrp.getAttributeValue(null, "value");
                        String[] colorVals = colorVal.split(" ");
                        colcoords[colorIndex++] = Float.parseFloat(colorVals[0]);
                        colcoords[colorIndex++] = Float.parseFloat(colorVals[1]);
                        colcoords[colorIndex++] = Float.parseFloat(colorVals[2]);
                        colcoords[colorIndex++] = 1f;
                    }
                }
                else if (xrp.getEventType() == XmlResourceParser.END_TAG)
                {
                    ;
                }
                else if (xrp.getEventType() == XmlResourceParser.TEXT)
                {
                    ;
                }
                xrp.next();
            }
            xrp.close();
        }
        catch (XmlPullParserException xppe)
        {
            Log.e("TAG", "Failure of .getEventType or .next, probably bad file format");
            xppe.toString();
        }
        catch (IOException ioe)
        {
            Log.e("TAG", "Unable to read resource file");
            ioe.printStackTrace();
        }
        mVertexBuffer = makeFloatBuffer(coords);
        mColorBuffer = makeFloatBuffer(colcoords);
        mNormalBuffer = makeFloatBuffer(ncoords);
        mIndexBuffer = makeShortBuffer(icoords);
    }

    private FloatBuffer makeFloatBuffer(float[] arr)
    {
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer fb = bb.asFloatBuffer();
        fb.put(arr);
        fb.position(0);
        return fb;
    }

    private ShortBuffer makeShortBuffer(short[] arr)
    {
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
        bb.order(ByteOrder.nativeOrder());
        ShortBuffer ib = bb.asShortBuffer();
        ib.put(arr);
        ib.position(0);
        return ib;
    }

    public void draw(GL10 gl)
    {
        gl.glFrontFace(GL10.GL_CCW);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
        gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
        gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalBuffer);
        gl.glDrawElements(GL10.GL_TRIANGLES, faceCount, GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
    }
}

First, the faces tag tells us how many faces are in the model so I can create an array the size of faces * 3 (three vertexes per face) to store the data in. The geometry tag tells us how many vertexes are in the model, to initialize the vertex, normal and color arrays. Faces are simply three vertex indexes, and are used in the glDrawElements command and are stored in the Index buffer. The vertexes have the three components of position x,y,z, normal x,y,z, and colour_diffuse r,g,b,a. There is also a texcoord u,v which is not used in this just to keep the code simpler, I plan to write about using textures in a later post.

Click here to download the entire Android project. This one I’m not putting on the Android Market, I don’t think it needs another Demo app. Hopefully this all makes sense and someone finds it useful. Leave a comment or email me if you have any questions or comments.

Part two of this using textures is here

28 Comments to Importing 3d Models in Android

  1. jabi's Gravatar jabi
    January 23, 2011 at 8:44 pm | Permalink

    Hi
    Thank you for your example.
    Come dressed as soon as I want to see the texture on the 3D model

  2. Ethan's Gravatar Ethan
    February 5, 2011 at 11:05 am | Permalink

    would you please contact me asap. I need you to do some blender models for me and send them to the android. Obviously for a price

    Thanks

  3. Light's Gravatar Light
    March 14, 2011 at 10:34 am | Permalink

    Can I ask you how to install orge??Because I linked to http://www.ogre3d.org/download/we-recommend,then I don’t know how to choose.Thanks a lot.

  4. Orlando's Gravatar Orlando
    March 29, 2011 at 8:39 am | Permalink

    Oh, Yours is really really helpful, Thanks for your sharing!!!

  5. BhavaniS's Gravatar BhavaniS
    May 24, 2011 at 8:00 am | Permalink

    Hi Idavey,
    This link is really useful.We were trying to simulate 3D chair(Irregular shape) in android using opengl. Using Blender 2.49b version, we were able to create XML files for the “.3DS” image as an input. Around four Mesh xml files were generated for Chair image[.3ds] using the OgreXMLConverter. Is it not something like a single xml file created for the entire complex 3D object?
    If not let us know the way in handling these four XML files in code?
    Thanks!

  6. August 3, 2011 at 6:53 am | Permalink

    I am having a bit of a problem and I’m not sure where it is stemming from. I have attempted now several times to export different 3D models to their mesh equivalents but every time I try and display them on the phone using your code the activity opens then immediately shuts. When I check log cat it says it crashed. It loads fine with your original colorcube.xml but all of the ones I have created are crashing before they are even getting started. Is this a limitation of the Drawmodel parser you wrote? to many indices or vertices?

  7. phil's Gravatar phil
    August 9, 2011 at 10:59 am | Permalink

    Hey,

    Thank you for the very helpful tutorial, in reply to Joey, I found that the DrawModel class was looking for geometry, but the XML file writes it as sharedgeometry, I renamed this on line 49 of DrawModel and it started working.

    Another interesting problem I overcame was I used my own model and in Blender I had not assigned any colors to the vertices so I all I got was black! Assigning colors using the vertice painter in Blender fixed that.

    However, something I am now stuck on is something I notice happening with your xml file as well, it is rendered in the app sideways (i.e. a different way “up”) to how it is rendered in blender, I have tried to figure out the cause to this but cannot, could you steer me in the right direction please?

    Many thanks,
    Phil,

  8. phil's Gravatar phil
    August 9, 2011 at 11:00 am | Permalink

    oh, and P.S. to BhavaniS, if you select all the objects that make your chair in Blender, and Ctrl + J to join them before you export, they will all export into a single XML file then :)

    Regards,
    Phil,

  9. August 13, 2011 at 9:18 pm | Permalink

    Phil, the OgreXMLConverter has an option to say if Y is the vertical or Z is the vertical. It’s the “Fix Up Axis to Y”, try toggling that and it should fix your issue.
    Thanks for your comments.
    Lee.

  10. minipot's Gravatar minipot
    August 26, 2011 at 5:01 am | Permalink

    how can I do this with 3d sequences to make like a human model that can run, jump and punch for a game

  11. Chris's Gravatar Chris
    September 27, 2011 at 9:17 pm | Permalink

    minipot,

    Typically I think you would use the external 3D program like Blender to create your basic 3D objects, then use Java and OpenGL to actually manipulate those objects for the animations desired once on the target platform.

    Chris

  12. September 27, 2011 at 9:43 pm | Permalink

    Some formats contain 3d sequences, such as md2. The format I’m using in this example is static. Manipulating each vertex to get some movement just isn’t realistic. I would recommend looking at another format which contains animations in it.
    Lee.

  13. October 27, 2011 at 6:07 am | Permalink

    Hello IDavey,

    Out of curiosity, why did you not use sketchup from google? Its free and it has a .dae (xml) format export. Or did you and then find that the models did not work? I ask because before I was pointed to your blog, I tried sketchup and it kind of worked for me. I am new to OpenGL so I am not sure if my errors are with the tool or simply with the output model.

    Here is the note I sent to android-developers

    Hi,

    I have a problem using opengl on android to draw a simple rectangle.
    This is what I have done.

    I drew a simple rectangle with sketchup. I exported the result using
    a 3d-model collada .dae file. I then copied the vertices data from
    the .dae (xml) file and put in an array. I copied the array in native
    format to a float buffer. I then drew the triangles using stripe
    mode. The result is nearly a rectangle. It is missing a triangle on
    each surface.

    Here is the relevant portion of code and the result.

    public void draw(GL10 gl) {
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
    // Enable color tracking
    gl.glEnable(GL10.GL_COLOR_MATERIAL);
    for (int i=0; i<108/4; i=i+4) {
    // while (i<(mVertexBuffer.limit()/3)){
    myDrawColor(gl,i);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,i,4);// mode, first, count

    }
    }

    the result is shown here
    http://imgur.com/a/o3HTP

    Please advise

    John

  14. January 7, 2012 at 4:01 am | Permalink

    Thanks Lee, i was just trying to find out if Ogre mesh loader is there or i have to start writing. Thanks again i can now use your source for Ogre meshes.

  15. Aditya Vashist's Gravatar Aditya Vashist
    February 6, 2012 at 4:32 am | Permalink

    I’ve been trying to use your code for reading some meshes that i exported from blender in xml format.But these xml’s are quiet big in size as compared to your cube.xml, android emulator is not able to display anything ,the screen goes black .
    Would appreciate if i could get some help in this issue and moreover would be thankfull if you could tell me a way to export meshes with animation to android (this is what i’ve been trying to do in my project).

  16. February 7, 2012 at 11:41 pm | Permalink

    In my cas it crashes, when i look at the XML file generated the vertices are written at the end, it is like all data is switched upside down, i am using blender 2.61 and blender2ogre-0.5.5, couldn’t get blender export to be recognised by blender, any tips? (CTRL+T also seems to have a different function in blender 2.61)

  17. jon's Gravatar jon
    February 24, 2012 at 10:08 am | Permalink

    how come when I import your project into eclipse, I get a bunch of errors?

  18. Lee Davey's Gravatar Lee Davey
    February 24, 2012 at 10:46 am | Permalink

    Hi guys,
    Looks like the new Blender is having troubles. I haven’t tried exporting a model in a while and I just installed the new version, I should have a fix up shortly (like in a few weeks).

    jon if you can email me the errors you’re getting in eclipse maybe I can help out.

    Lee.

  19. jon's Gravatar jon
    February 24, 2012 at 2:58 pm | Permalink

    I don’t see your email anywhere so here are the errors.
    I am new to app development so maybe I am doing somthing wrong?
    I just imported the project into eclipse and this is what I get.
    http://cgfix.com/3dmesherrors.wmv

  20. Lee Davey's Gravatar Lee Davey
    February 24, 2012 at 4:18 pm | Permalink

    Jon,
    That’s weird. It’s like the DrawModel class is not including any of the standard java class libraries. What’s odd is the main activity is fine. Try cleaning the project if you haven’t already (project > clean). Let me know how that goes and email me back at lee.davey@gmail.com (apparently my wordpress blog doesn’t post the email when you enter it in the reply)
    Lee.

  21. jon's Gravatar jon
    February 25, 2012 at 12:13 am | Permalink

    It actually has errors too. When you expand it,
    http://www.cgfix.com/Capture.PNG
    The console says this too.
    [2012-02-24 23:07:25 - AndroidOgreMesh] Unable to resolve target ‘android-5′

    The same thing happened when I tried to import a similar project off the web. But when I follow the tutorials from android.com they work fine.

  22. Lee Davey's Gravatar Lee Davey
    February 28, 2012 at 8:07 am | Permalink

    Jon,
    I think I got it. Looks like my project is built for a certain version of Android that you might not have installed. Right click on the project on the left side and select properties. Then in the new window, click on Android. You’ll have a list of Android build targets you can select from. If that doesn’t work, try also changing your min sdk and target sdk in the project manifest.
    Hopefully that helps.
    Lee.

  23. m0skit0's Gravatar m0skit0
    April 21, 2012 at 4:39 am | Permalink

    Great, thank you very much. I’m reusing some of your code for my project :)

Leave a Reply

You can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Pages

Blogroll