JME2 Starter 6 - Hello ModelLoading


« Previous: Starter Tutorial 5 - Hello KeyInput
Next: Starter Tutorial 7 - Hello MousePick »


(Tip: Up-to-date source files for the tutorials are always in the repository)


This example introduces BinaryImporter and FileConverter. You will learn how to load 3D object formats into a jME scene graph. Pretty short program, eh?

Sample Code

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
 
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.scene.Node;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.model.converters.FormatConverter;
import com.jmex.model.converters.ObjToJme;
 
/**
 * Started Date: Jul 22, 2004<br>
 * <br>
 * 
 * Demonstrates loading formats.
 * 
 * @author Jack Lindamood
 */
public class HelloModelLoading extends SimpleGame {
	private static final Logger logger = Logger
		.getLogger(HelloModelLoading.class.getName());
 
	public static void main(String[] args) {
		HelloModelLoading app = new HelloModelLoading();
		app.setConfigShowMode(ConfigShowMode.AlwaysShow);
		// Turn the logger off so we can see the XML later on
		app.start();
	}
 
	protected void simpleInitGame() {
		// Point to a URL of my model
		URL model = HelloModelLoading.class.getClassLoader().getResource(
			"jmetest/data/model/maggie.obj");
 
		// Create something to convert .obj format to .jme
		FormatConverter converter = new ObjToJme();
		// Point the converter to where it will find the .mtl file from
		converter.setProperty("mtllib", model);
 
		// This byte array will hold my .jme file
		ByteArrayOutputStream BO = new ByteArrayOutputStream();
		try {
			// Use the format converter to convert .obj to .jme
			converter.convert(model.openStream(), BO);
			Node maggie = (Node) BinaryImporter.getInstance().load(
				new ByteArrayInputStream(BO.toByteArray()));
			// shrink this baby down some
			maggie.setLocalScale(.1f);
			maggie.setModelBound(new BoundingSphere());
			maggie.updateModelBound();
			// Put her on the scene graph
			rootNode.attachChild(maggie);
		} catch (IOException e) { // Just in case anything happens
			logger.logp(Level.SEVERE, this.getClass().toString(),
				"simpleInitGame()", "Exception", e);
			System.exit(0);
		}
	}
}

(Screenshot taken from jME 1)

FormatConverter

To understand file loading with jME, one must understand that jME doesn’t support loading any file format directly, other than its own binary format called “jME binary.” But there are converter classes, such as ObjToJme (jME API) or MaxToJme (jME API) or Md2ToJme (jME API), that convert from the given format to jME’s binary format. They all extend FormatConverter (jME API). Because the file we’re using in this example is an .obj format file, we use ObjToJme to convert it to binary.

// Create something to convert .obj format to .jme
FormatConverter converter = new ObjToJme();

Normally, it is sufficient to simply convert without setting any properties. The .obj format is different though because it splits its information between a .obj file and a .mtl file. The .obj holds geometry and the .mtl holds material information. Because of this, we have to set a property to tell the converter which directory to find .mtl libraries in.

// Point the converter to where it will find the .mtl file from
converter.setProperty("mtllib", model);

To learn which properties are needed for which formats, consult the javadoc of each format you’re using.

InputStreams and OutputStreams

jME works purely with streams for converting files: InputStreams to read files and OutputStreams to send their contents. What we are doing in this example, is simply writing the model to a byte stream:

// This byte array will hold my .jme file
ByteArrayOutputStream BO = new ByteArrayOutputStream();

Then we just call convert and read from the stream:

// Use the format converter to convert .obj to .jme
converter.convert(model.openStream(), BO);

Convert takes an InputStream which contains the model (.obj or .3ds or .md3 or .ms3d or whatever), and sends it to an OutputStream as .jme. If anything goes wrong with converting or reading a file, an IOException is thrown, which is why I have to surround it with a try/catch. After the conversion, the BO stream now contains the model, but in jME’s binary format.

FIXME: If you abort a program, you should exit with non-zero exit value. Tthis program aborts with 0.

Loading the Model into a Node

Now, we use the BinaryImporter (jME API) class to read the object's binary format and load it into a Node in the scene graph.

Node maggie = (Node)BinaryImporter.getInstance().load(new ByteArrayInputStream(BO.toByteArray()));

That's it! If the node is attached to the rootNode, the model should appear now.

FileInputStream and FileOutputStream

The method used in this example works well during the development phase. You may still be modifying and adjusting your models and you want to reload them every time. But for the release you should convert all your models to jME’s binary format, and read that directly.


Tip: The best practice is to write a method that converts all model files to .jme files, and save them using FileOutputStreams. When your game starts, you load the .jme file using a FileInputStream. This way you increase your application's performance, because the user doesn't need to wait for the files to be converted.

The File Loading System

Here is a flow chart of jME’s file loading system. Note that there are more than just 2 supported formats. I only list .obj and .3ds here for the sake of size.

 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution 3.0 Unported