Creating a basic M3G file with Blender

When using the Mobile 3D Graphics API (JSR 184), it’s important to understand how to create 3D objects, as discussed in my last couple of posts. However, in a real application you will typically use files created by 3D modeling software.

Many commercial software suites allow you to export 3D scenes in M3G format (required by JSR 184), including a free one called Blender, which you can download here. Don’t let the fact that it’s free fool you — like a lot of free software, it’s a full-featured software package suitable for professional use. By default Blender doesn’t export files in M3G format — you have to install an additional plugin such as the one I found from Nelson Games here. The plugin is very easy to install, you just need to make sure that Python (version 2.4 or greater) is correctly installed, and then you place the additional script in the Blender python scripts folder.

As the Blender documentation freely admits, Blender is not terribly newbie-friendly(really it could stand a few basic tutorials…) but it’s quite powerful and configurable once you get the hang of it. Here are some basic steps to get started and create a simple M3G file:

screenshotblendercube.png

This is what you will see when you first open Blender. (Click on the image to see it full-size.)

A new file in Blender starts off with a cube near the origin. You can see what the cube looks like when rendered by selecting render. The camera used for this rendering is that little line drawing near the bottom right corner.

If you don’t feel like using the default cube, just get rid of it by just pressing the delete key. (Since the cube is initially selected by default, the delete key deletes it.) If you’d like to keep it instead, you can transform it. If you’re in object or edit mode (the first drop-down menu in the toolbar along the bottom in the above screenshot is the mode), you’ll see some little buttons with icons for different ways of transforming the objcet: a triangle for translation, a donut for rotation, and a square for scale. To add further objects, go to add > mesh as seen in the following screenshot:

screenshotaddmesh.png

At this point you have the idea of how to create a very basic file, and you can try fiddling with all of the various gizmos on your own to see what else you can do. 😀

Once you have your scene ready, you can export it in M3G format by selecting M3G under the file > export menu. This option will appear automatically if the plugin is correctly installed. If you’re using the same plugin I’ve recommended, you’ll have a choice between exporting as M3G or as Java code. The Java code option is what I like about this particular plugin (I don’t know if others offer it). But let’s start by exporting it as M3G since that’s what you’ll usually do in a real application, and talk about exporting it as Java code later in this example.

Rendering the M3G file in your Java application is even easier than creating it! (Although — as with creating the file — there’s no end to the possibilities once you get started and get the hang of it.) All you do is put your M3G file in the MIDlet’s Jar file, load it with the Loader class, find the World node, and render it using the Graphics3D class.

There are a bunch of tutorials that show these basic steps, but I’ll show them here for completeness since there are only a few essential lines. Let’s assume we called the file “cube.m3g”. Here’s the code for the initialization step:

Object3D[] allNodes = Loader.load(“/cube.m3g”);

// find the world node
for(int i = 0, j = 0; i < allNodes.length; i++) {
if(allNodes[i] instanceof World) {
myWorld = (World)allNodes[i];
}
}

Then in the paint method of the Canvas (or other rendering target), you start by binding the Graphics3D singleton to the target’s Graphics instance, then call render on the World you read from the file, then release the target. You can do more entertaining things by manipulating the scene graph that is accessible from the World node, but if all you want to do is display the image file you created, then you’re done.

/**
* Paint the graphics onto the screen.
*/
protected void paint(Graphics g) {
Graphics3D g3d = null;
try {
// Start by getting a handle to the Graphics3D
// object which does the work of projecting the
// 3-D scene onto the 2-D screen (rendering):
g3d = Graphics3D.getInstance();
// Bind the Graphics3D object to the Graphics
// instance of the current canvas:
g3d.bindTarget(g);

// Now render: (project from 3D scene to 2D screen)
g3d.render(myWorld);

} catch(Exception e) {
e.printStackTrace();
} finally {
// Done, the canvas graphics can be freed now:
g3d.releaseTarget();
}
}

In my earlier examples we had to define the camera, but when rendering a world node like this, the default camera is defined in the world’s data. This mode of rendering is called “retained mode” (the earlier examples were “immediate mode”). A typical way to think about these two modes is that immediate mode is what you would use when defining a simple object in the code whereas retained mode is what you use for rendering M3G files. That’s what it usually comes down to in practice, but that isn’t the real difference, especially since you can define a world node and complete scene graph in the code and render it in retained mode or you could extract the VertexBuffer and IndexBuffer from a Mesh you found in an M3G file and render it in immediate mode if you like.

The fact that you can create the same data in Java code as you can define in an M3G file becomes very apparent if you export your scene from Blender as Java code. In fact, that way you can look at the code and see how it’s done, tweaking it and seeing the effects of your modifications if you like. Even though the Java class generally takes more space in the jar (even after obfuscation), there are real-world applications for exporting the file as Java code since on many platforms Java code loads faster than resources from the Jar file.

The Java code produced by this plugin is quite easy to use. It creates a Java class with the name you choose when you export, and the class has a static “getRoot” method that returns the world node. The only things I had to change to get it to work were to add it to my package and change the use of “Canvas3D” to “Canvas”. (I’m not sure why Canvas3D is used here since Canvas is more widely supported and works just the same.)

If you’d like to have a look at the code it generates, I’m posting the exported code of a simple Blender-generated cube below the fold.

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Image;
import javax.microedition.m3g.*;
public final class cube {
public static World getRoot(Canvas aCanvas) {
//Camera Camera
Camera BL2 = new Camera();
float[] BL2_matrix = {
0.685880541801f,-0.317370116711f,0.654861867428f,7.48113155365f,
0.727633774281f,0.312468618155f,-0.610665619373f,-6.50763988495f,
-0.0108167808503f,0.895343244076f,0.445245355368f,5.34366512299f,
0.0f,0.0f,0.0f,1.0f
};

Transform BL2_transform = new Transform();
BL2_transform.set(BL2_matrix);
BL2.setTransform(BL2_transform);

BL2.setPerspective(60.000000f, //Field of View
(float)aCanvas.getWidth()/(float)aCanvas.getHeight(),
0.10000000149f, //Near Clipping Plane
100.0f); //Far Clipping Plane
//Background:
Background BL3 = new Background();
BL3.setColor(0x000E3866);

//Camera Camera.001
Camera BL4 = new Camera();
float[] BL4_matrix = {
1.0f,0.0f,0.0f,0.0f,
0.0f,1.0f,0.0f,0.0f,
0.0f,0.0f,1.0f,0.0f,
0.0f,0.0f,0.0f,1.0f
};

Transform BL4_transform = new Transform();
BL4_transform.set(BL4_matrix);
BL4.setTransform(BL4_transform);

BL4.setPerspective(60.000000f, //Field of View
(float)aCanvas.getWidth()/(float)aCanvas.getHeight(),
0.10000000149f, //Near Clipping Plane
100.0f); //Far Clipping Plane
//Light: Lamp.001
Light BL5 = new Light();
BL5.setMode(131);
BL5.setAttenuation(0.000000f, 0.100000f,0.000000f);
BL5.setColor(0x00FFFFFF);
BL5.setIntensity(1.000000f);
BL5.setSpotAngle(45.000000f);
BL5.setSpotExponent(0.150000f);
float[] BL5_matrix = {
0.128675252199f,0.0f,0.0f,0.0f,
0.0f,0.122168809175f,0.0f,0.0f,
0.0f,0.0f,1.0f,0.0f,
0.0f,0.0f,0.0f,1.0f
};

Transform BL5_transform = new Transform();
BL5_transform.set(BL5_matrix);
BL5.setTransform(BL5_transform);

// VertexArray
short[] BL6_array = {
32766,32766,-32766,32766,-32766,-32766,-32766,-32766,-32766,-32766,32766,-32766,
32766,32766,32766,-32766,32766,32766,-32766,-32766,32766,32766,-32766,32766,
32766,32766,-32766,32766,32766,32766,32766,-32766,32766,32766,-32766,-32766,
32766,-32766,-32766,32766,-32766,32766,-32766,-32766,32766,-32766,-32766,-32766,
-32766,-32766,-32766,-32766,-32766,32766,-32766,32766,32766,-32766,32766,-32766,
32766,32766,32766,32766,32766,-32766,-32766,32766,-32766,-32766,32766,32766
};

VertexArray BL6 = new VertexArray(BL6_array.length/3,3,2);
BL6.set(0,BL6_array.length/3,BL6_array);

// VertexArray
byte[] BL7_array = {
0,0,-127,0,0,-127,0,0,-127,0,0,-127,
0,0,127,0,0,127,0,0,127,0,0,127,
127,0,0,127,0,0,127,0,0,127,0,0,
0,-127,0,0,-127,0,0,-127,0,0,-127,0,
-127,0,0,-127,0,0,-127,0,0,-127,0,0,
0,127,0,0,127,0,0,127,0,0,127,0
};

VertexArray BL7 = new VertexArray(BL7_array.length/3,3,1);
BL7.set(0,BL7_array.length/3,BL7_array);

//VertexBuffer
VertexBuffer BL8 = new VertexBuffer();
float BL8_Bias[] = { 0.000000f, -0.000000f, 0.000000f};
BL8.setPositions(BL6,0.000031f,BL8_Bias);
BL8.setNormals(BL7);
//length of TriangleStrips
int[] BL9_stripLength ={4,4,4,4,4,4};

//IndexBuffer
int[] BL9_Indices = {
1,2,0,3,5,6,4,7,9,10,8,11,13,14,12,15,17,18,16,19,21,22,20,23};

IndexBuffer BL9=new TriangleStripArray(BL9_Indices,BL9_stripLength);

PolygonMode BL10 = new PolygonMode();
BL10.setCulling(162);
BL10.setShading(165);
BL10.setWinding(168);
BL10.setTwoSidedLightingEnable(false);
BL10.setLocalCameraLightingEnable(false);
BL10.setPerspectiveCorrectionEnable(false);

//Material: Material
Material BL11 = new Material();
BL11.setColor(Material.AMBIENT,0x00000000);
BL11.setColor(Material.SPECULAR,0x00000000);
BL11.setColor(Material.DIFFUSE,0xFFCCCCCC);
BL11.setColor(Material.EMISSIVE,0x00000000);
BL11.setShininess(0.000000f);
BL11.setVertexColorTrackingEnable(false);
//Appearance
Appearance BL12 = new Appearance();
BL12.setPolygonMode(BL10);
BL12.setMaterial(BL11);

//Mesh:Cube
Mesh BL13 = new Mesh(BL8,BL9,BL12);
float[] BL13_matrix = {
1.0f,0.0f,0.0f,0.0f,
0.0f,1.0f,0.0f,0.0f,
0.0f,0.0f,1.0f,0.0f,
0.0f,0.0f,0.0f,1.0f
};

Transform BL13_transform = new Transform();
BL13_transform.set(BL13_matrix);
BL13.setTransform(BL13_transform);

//Light: Lamp
Light BL14 = new Light();
BL14.setMode(130);
BL14.setAttenuation(0.000000f, 0.066667f,0.000000f);
BL14.setColor(0x00FFFFFF);
BL14.setIntensity(1.000000f);
float[] BL14_matrix = {
-0.290864646435f,-0.771100819111f,0.566393196583f,4.07624530792f,
0.95517116785f,-0.19988335669f,0.21839119494f,1.00545394421f,
-0.0551890581846f,0.604524731636f,0.794672250748f,5.90386199951f,
0.0f,0.0f,0.0f,1.0f
};

Transform BL14_transform = new Transform();
BL14_transform.set(BL14_matrix);
BL14.setTransform(BL14_transform);

//World:
World BL15 = new World();
BL15.addChild(BL4);
BL15.addChild(BL5);
BL15.addChild(BL13);
BL15.addChild(BL14);
BL15.addChild(BL2);
BL15.setBackground(BL3);
BL15.setActiveCamera(BL2);

return BL15;
}
}

 

Advertisements

11 comments so far

  1. George on

    Nice blog and nice article!

    But… a question, could be possible for you write a post about a textured cube.

    Thank you very much!!

  2. carolhamer on

    Thanks George!

    In my book, I covered adding texture to a pyramid (which is essentially the same as adding it to a cube). It’s fairly straight-forward. You can download the source code in the “book extras” here and have a look.

  3. David D Brown on

    8/3/08
    Hi,
    I am new to this.After I export from Blender,
    what file do I put it in the game folder?
    res file for images
    src file for the digital
    Is there a tutorial for the placement of the Blender export to the Sun Emulator program?
    Thank You,
    David

  4. carolhamer on

    David — If you’re using the WTK with KToolbar to build your jar, then the images created by Blender go in the res folder, and a src file created by Blender (such as the one in this post) would go in the src folder.

  5. david on

    and what about the camara its the same in the m3G modeling than the java program in canvas?? and how i can move the camera around the m3g object

    thanx david.

  6. Rubén on

    Hi, I have a trouble with Blender’s exports, I had a simple j2me project that makes spin a cube, I want to change this cube (cube.m3g) with a figure that I did (zerg.m3g), I did it with VRML and after I imported it to Blender (For exort it as m3g file). But when I run the changed project it appears an error:
    null
    java.lang.NullPointerException
    java.lang.NullPointerException
    at M3GCanvas.moveCube(M3GCanvas.java:170)
    at M3GCanvas.draw(M3GCanvas.java:141)
    at M3GCanvas.run(M3GCanvas.java:212)

    where in the mentioned line 170 is this:
    cube.preRotate(5.0f, 0.0f, 0.0f, 1.0f);
    in line 141 is the call to moveCube function:
    moveCube();
    and in line 212 is:
    draw(getGraphics());
    Please help me.

  7. carolhamer on

    To get a NullPointerException on the line “cube.preRotate(5.0f, 0.0f, 0.0f, 1.0f);” my guess is that “cube” is null. Perhaps it wasn’t initialized correctly. Note that the Blender-generated public static final class here is called “cube” — perhaps Blender changed the name to “zerg” when exporting it. If so, changing “cube” to “zerg” elsewhere should fix the problem.

  8. Amit on

    Hi, I have copied your code (java file – Cube)
    trying to run the same but getting the following exception
    Exception in thread “main” java.lang.UnsatisfiedLinkError: javax.microedition.m3g.Engine.cacheFID(Ljava/lang/Class;I)V

    Have you come across such exception, if yes then please let me know the steps to correct the same.

    thanks in Advance
    Amit

  9. Evan on

    I’m a rank newbie here.
    What will be the name of the file I’ll be saving it in? Usually you name the file after whatever class is extending the midlet class, but I don’t see a midlet class being extended.

  10. Evan on

    Dumb question. Never mind. You need a driver to run it. Duh!

  11. eDuardoAg on

    man i need your help, you say it would be possible to extract the VertexBuffer and indexbuffer from a mesh inside a m3g and render it on inmediate mode, but how do you do this, i’ve been trying to get a mesh from a world and i cant find a single method to do this, it only gives me a Node when i call getChild(0); but i want a MESH with its VertexBuffer and IndexBuffer have you tried to do this?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: