Importing 3d Models in Android

This post is out of date and I was not able to get the current version of Blender working with an Ogre Mesh export. Update coming soon using Wavefront export.

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

46 thoughts on “Importing 3d Models in Android

  1. 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

  2. That’s a really good question. I struggled to find out where I found the OgreXMLConverter.exe in my PythonOgreRelease directory. I did find this, http://www.ogre3d.org/tikiwiki/OgreXmlConverter&structure=Tools
    This is the page which links to the documentation for that tool and the download site for the msi. Unfortunately, when I tried to install the MSI I had troubles finding OgreXMLConverter.exe. If you have the same problem let me know and I can email you the file directly.

  3. 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!

  4. Sorry about the late reply. It sounds like blender and OgreXMLConverter is converting the 3ds file into multiple meshes. Using the code I have, you’d have to create a DrawModel for each xml file and then draw all four of them.

  5. 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?

  6. 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,

  7. 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,

  8. 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.

  9. Joey,
    My xml parser isn’t very robust. It looks for one set of faces and geometry. One issue could be that you have multiple meshes and that’s where the code might fall down with an array out of bounds. I only allocate the faces and geometry arrays once. If you email me your xml I could have a look or the stack trace as well.
    Thanks,
    Lee.

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

  11. 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. 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. 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. John,
    At one point I started using Sketchup instead of Blender because of it’s ease of use. There is even an ogre mesh exporter for Sketchup, I didn’t know the dae file was xml. The issue I have with Blender is texture mapping, I struggled with figuring out how to do a proper UV map (not just a repeating texture map like most of it seems like). With Blender I can really be specific about how the texture fits onto the 3d model. Blender might have this functionality but I couldn’t find it in the time I played around with it. I just saw that version 8 of Sketchup is out, I might have a poke around it.
    Regarding your code, it’s hard to say without seeing the xml. It does seem to follow a pattern and it looks like the vertices are not exported to work with TRIANGLE STRIP. They should be in a Z order for each side but this looks like they are exported in a C order for each side (I hope that makes sense). I’ve also seen with Blender that it defaults to using QUADS and you have to tell it to export to triangles.
    Email me your xml file or any other questions, lee.davey@gmail.com
    Thanks for your comments.
    Lee

  15. John, I just tried out using DAE exports from blender and taking the raw export (renaming it to an xml file) and dropping it into my Android project gets me an error in the project. It looks like the dae xml format isn’t liked by Android. I’m going to play with it some more and see if I can get it going.
    Lee.

  16. 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.

  17. 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).

  18. 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)

  19. 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.

  20. 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. 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. 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. I want to export 3D rigged human . I went following way, but blender don’t take it as input for exporting , Not selected ,not updated. Then when press export see

    Exporting…..
    warning no mesh object selected for export
    done

    can anyone give me the solution??

  24. Ahaa, its pleasant dialogue rеgarding this post аt this place at thіs
    website, ӏ haѵe гead all that, ѕо at thiѕ tіme me also commеnting hеre.

    Feel free to vіsit my hοmepage Rosemarie

  25. Can i ask what will i change on the DrawModels code if i will import other 3D models? im having an error on that.. thanks

  26. I am using 3ds max 2010. How can I export my max file to .mesh.xml? Which tool can I use? I already tried material xml exporter from 3ds max utilities. It didn’t work.

  27. i can’t find the ogre xml converter from the source you have given : ” http://www.ogre3d.org/tikiwiki/OgreXmlConverter&structure=Tools ” the file is not there. could you please send the converter to my mail id (safvan4u@gmail.com). that will be really helpful. and by the way this article will be massively helpful to whoever looking for a way to implement a 3d model in android. Thanks a lot… :)

  28. Hi everyone, it’s my first go to see at this website, and piece of writing is really fruitful for me, keep up posting these types of articles.

  29. Society has accepteddieting pillsas a form of losing weight.
    When dieting pills you may havedeveloped negative thought patterns about
    food and eating. Avoid all foods that are sure to help you.
    However, if you eat blueberries, you will not get the same results you would
    have formerly put on! Though participants typically lost weight over the eight week diet, most regained a
    substantial amount of it over the next year and many reported feeling hungrier and
    more food obsessed than before the diet.

  30. Hi guys,
    really thanks a lot for this tutorial on how t import 3d models!!! Really good job!!
    But i have a proble: i follow the above instructions and run the android application in order to see the 3d colorcub, but the only thing i see is a black screen!
    How can i overcome this problem???
    Looking forward to your reply
    Thank you in advance!

  31. Guys,
    need help again! I have managed finally to run the application with the colorcube and it’s nice and runs very good!
    I have tried to replace the colorcube.xml with a xml file from a 3d model that i have created with solidworks.. I make the mesh with ogre with no errors and move this xml at the same folder with colorcube.xml.
    I change the reference at the code from: model = new DrawModel(context.getResources().getXml(R.xml.colorcube));
    to have the right .xml file and run it as application..
    But nothing appears on the screen.. my 3d model is missing…

    Any ideas what might goes wrong??
    Thanks in advance

  32. Hi there, I do believe your web site may be having
    web browser compatibility issues. When I look at your
    website in Safari, it looks fine however, if opening in IE, it
    has some overlapping issues. I simply wanted to give you a quick heads up!
    Apart from that, great site!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may 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>